vite-plugin-cpp-loader 2.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # vite-cpp-loader
2
+
3
+ Import `.cpp` files directly into TypeScript. Functions compile to WebAssembly and are callable with full type safety.
4
+
5
+ ## Prerequisites
6
+
7
+ Emscripten must be installed and `em++` available in your `PATH`.
8
+
9
+ **macOS:**
10
+ ```bash
11
+ brew install emscripten
12
+ ```
13
+
14
+ **Linux / macOS (via emsdk):**
15
+ ```bash
16
+ git clone https://github.com/emscripten-core/emsdk.git
17
+ cd emsdk && ./emsdk install latest && ./emsdk activate latest
18
+ source ./emsdk_env.sh
19
+ ```
20
+
21
+ **Windows:**
22
+ ```bash
23
+ git clone https://github.com/emscripten-core/emsdk.git
24
+ cd emsdk && emsdk install latest && emsdk activate latest && emsdk_env.bat
25
+ ```
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install vite-plugin-cpp-loader --save-dev
31
+ ```
32
+
33
+ ## Setup
34
+
35
+ ```typescript
36
+ // vite.config.ts
37
+ import { defineConfig } from 'vite';
38
+ import cppLoader from 'vite-plugin-cpp-loader';
39
+
40
+ export default defineConfig({
41
+ plugins: [cppLoader()]
42
+ });
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ Write plain C++ — no Emscripten boilerplate required. All top-level non-static functions are automatically exported.
48
+
49
+ **`math.cpp`**
50
+ ```cpp
51
+ int add(int a, int b) {
52
+ return a + b;
53
+ }
54
+ ```
55
+
56
+ **`main.ts`**
57
+ ```typescript
58
+ import { add } from './math.cpp';
59
+
60
+ add(2, 3); // 5
61
+ ```
62
+
63
+ If you only want to export a subset of functions, mark the ones you want to keep internal as `static` and they will be excluded.
64
+
65
+ ## Type safety
66
+
67
+ Types are inferred from your C++ signatures:
68
+
69
+ ```typescript
70
+ import { add } from './math.cpp';
71
+
72
+ add(2, 3); // ✅
73
+ add("2", 3); // ❌ Argument of type 'string' is not assignable to parameter of type 'number'
74
+ ```
75
+
76
+ Supported C++ → TypeScript mappings:
77
+
78
+ | C++ | TypeScript |
79
+ |-----|------------|
80
+ | `int`, `float`, `double`, `long`, `short`, `unsigned` | `number` |
81
+ | `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, `size_t` | `number` |
82
+ | `bool` | `boolean` |
83
+ | `std::string`, `const char*` | `string` |
84
+ | `void` | `void` |
85
+ | `emscripten::val` | `any` |
86
+
87
+ Complex types (STL containers, custom classes) are not supported as parameters or return types.
88
+
89
+ ## Options
90
+
91
+ ```typescript
92
+ cppLoader({
93
+ emccArgs: ['-sASSERTIONS=1'], // extra flags passed to em++
94
+ optimizationLevel: 'O3', // default: 'O2'
95
+ cacheDir: '.my-wasm-cache', // default: 'node_modules/.cpp-wasm'
96
+ setupVSCode: false, // disable auto-generation of .vscode/c_cpp_properties.json
97
+ })
98
+ ```
99
+
100
+ ## Troubleshooting
101
+
102
+ **`em++` not found** — make sure Emscripten is installed and in your `PATH`. If you installed via emsdk, run `source ./emsdk_env.sh` before starting the dev server.
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,26 @@
1
+ import { type SpawnOptions } from 'node:child_process';
2
+ import type { CompileResult, CppLoaderOptions } from './types';
3
+ export interface ResolvedOptions extends Required<CppLoaderOptions> {
4
+ cacheDir: string;
5
+ }
6
+ export interface SpawnResult {
7
+ code: number;
8
+ stderr: string;
9
+ }
10
+ type SpawnFn = (cmd: string, args: string[], opts?: SpawnOptions) => Promise<SpawnResult>;
11
+ type FsReadFile = (p: string) => Promise<Buffer | string>;
12
+ type FsWriteFile = (p: string, data: string) => Promise<void>;
13
+ type FsMkdir = (p: string, opts?: {
14
+ recursive?: boolean;
15
+ }) => Promise<unknown>;
16
+ type FsStat = (p: string) => Promise<unknown>;
17
+ export interface CompilerDeps {
18
+ spawnFn?: SpawnFn;
19
+ readFile?: FsReadFile;
20
+ writeFile?: FsWriteFile;
21
+ mkdir?: FsMkdir;
22
+ stat?: FsStat;
23
+ }
24
+ export declare function defaultSpawn(cmd: string, args: string[], opts?: SpawnOptions): Promise<SpawnResult>;
25
+ export declare function compile(id: string, options: ResolvedOptions, isServe: boolean, overrides?: CompilerDeps): Promise<CompileResult>;
26
+ export {};
@@ -0,0 +1,92 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { promises as fs } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { readManifest, scheduleManifestWrite, isCacheValid } from './manifest';
6
+ import { extractTopLevelFunctionNames, buildAutoBindings } from './parser';
7
+ const inFlight = new Map();
8
+ export function defaultSpawn(cmd, args, opts) {
9
+ return new Promise((resolve, reject) => {
10
+ const child = spawn(cmd, args, { stdio: ['ignore', 'inherit', 'pipe'], ...opts });
11
+ const stderrChunks = [];
12
+ child.stderr?.on('data', (chunk) => {
13
+ stderrChunks.push(chunk);
14
+ process.stderr.write(chunk);
15
+ });
16
+ child.on('error', (err) => {
17
+ if (err.code === 'ENOENT') {
18
+ reject(new Error('em++ not found. Ensure Emscripten is installed and in your PATH.'));
19
+ }
20
+ else {
21
+ reject(err);
22
+ }
23
+ });
24
+ child.on('close', code => {
25
+ resolve({ code: code ?? 1, stderr: Buffer.concat(stderrChunks).toString() });
26
+ });
27
+ });
28
+ }
29
+ function hashContent(content) {
30
+ return createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 16);
31
+ }
32
+ async function prepareTempFile(id, content, options, deps) {
33
+ if (content.includes('EMSCRIPTEN_BINDINGS')) {
34
+ return { compileId: id, cleanup: async () => { } };
35
+ }
36
+ const names = extractTopLevelFunctionNames(content);
37
+ const tmpPath = path.join(options.cacheDir, path.basename(id, '.cpp') + '__tmp.cpp');
38
+ await deps.writeFile(tmpPath, content + buildAutoBindings(names));
39
+ return { compileId: tmpPath, cleanup: () => fs.unlink(tmpPath).catch(() => { }) };
40
+ }
41
+ async function doCompile(id, options, isServe, deps) {
42
+ const content = (await deps.readFile(id)).toString();
43
+ const hash = hashContent(content);
44
+ await deps.mkdir(options.cacheDir, { recursive: true });
45
+ const manifest = await readManifest(options.cacheDir, deps.readFile);
46
+ const cached = manifest[id];
47
+ if (await isCacheValid(cached, hash, deps.stat)) {
48
+ return { jsPath: cached.jsPath, tsdPath: cached.tsdPath };
49
+ }
50
+ const baseName = path.basename(id, '.cpp');
51
+ const jsPath = path.join(options.cacheDir, baseName + '.js');
52
+ const tsdPath = path.join(options.cacheDir, baseName + '.tsd.d.ts');
53
+ const { compileId, cleanup } = await prepareTempFile(id, content, options, deps);
54
+ const args = [
55
+ compileId,
56
+ '-o', jsPath,
57
+ `-${options.optimizationLevel}`,
58
+ '-sEXPORT_ES6',
59
+ '-sMODULARIZE',
60
+ '-sENVIRONMENT=web',
61
+ '-sALLOW_MEMORY_GROWTH',
62
+ '-lembind',
63
+ '--emit-tsd', tsdPath,
64
+ ...(isServe ? [] : ['-sSINGLE_FILE']),
65
+ ...options.emccArgs,
66
+ ];
67
+ const result = await deps.spawnFn('em++', args);
68
+ await cleanup();
69
+ if (result.code !== 0) {
70
+ throw new Error(`em++ failed to compile ${path.basename(id)}:\n${result.stderr}`);
71
+ }
72
+ manifest[id] = { hash, jsPath, tsdPath };
73
+ scheduleManifestWrite(options.cacheDir, manifest, deps.writeFile);
74
+ return { jsPath, tsdPath };
75
+ }
76
+ export function compile(id, options, isServe, overrides = {}) {
77
+ const existing = inFlight.get(id);
78
+ if (existing)
79
+ return existing;
80
+ const deps = {
81
+ spawnFn: overrides.spawnFn ?? defaultSpawn,
82
+ readFile: overrides.readFile ?? ((p) => fs.readFile(p)),
83
+ writeFile: overrides.writeFile ?? ((p, d) => fs.writeFile(p, d)),
84
+ mkdir: overrides.mkdir ?? ((p, o) => fs.mkdir(p, o)),
85
+ stat: overrides.stat ?? ((p) => fs.stat(p)),
86
+ };
87
+ const promise = doCompile(id, options, isServe, deps).finally(() => {
88
+ inFlight.delete(id);
89
+ });
90
+ inFlight.set(id, promise);
91
+ return promise;
92
+ }
@@ -0,0 +1,2 @@
1
+ import type { CppFunction } from './types';
2
+ export declare function generateDeclarations(functions: CppFunction[]): string;
@@ -0,0 +1,7 @@
1
+ export function generateDeclarations(functions) {
2
+ const lines = functions.map(func => {
3
+ const params = func.parameters.map(p => `${p.name}: ${p.type}`).join(', ');
4
+ return `export declare function ${func.name}(${params}): ${func.returnType};`;
5
+ });
6
+ return lines.join('\n') + '\n';
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { CppLoaderOptions } from './types';
3
+ export default function cppLoader(options?: CppLoaderOptions): Plugin;
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { compile } from './compiler';
4
+ import { extractFunctions } from './parser';
5
+ import { generateDeclarations } from './declarations';
6
+ import { generateModule } from './moduleTemplate';
7
+ import { generateCppProperties } from './vscode';
8
+ const DEFAULT_CACHE_DIR = 'node_modules/.cpp-wasm';
9
+ const DEFAULT_OPT_LEVEL = 'O2';
10
+ export default function cppLoader(options = {}) {
11
+ let isServe = false;
12
+ let projectRoot = process.cwd();
13
+ const setupVSCode = options.setupVSCode !== false;
14
+ const resolvedOptions = {
15
+ emccArgs: options.emccArgs ?? [],
16
+ cacheDir: path.resolve(options.cacheDir ?? DEFAULT_CACHE_DIR),
17
+ optimizationLevel: options.optimizationLevel ?? DEFAULT_OPT_LEVEL,
18
+ setupVSCode,
19
+ };
20
+ return {
21
+ name: 'vite-cpp-loader',
22
+ configResolved(config) {
23
+ isServe = config.command === 'serve';
24
+ projectRoot = config.root;
25
+ },
26
+ buildStart() {
27
+ if (setupVSCode)
28
+ generateCppProperties(projectRoot).catch(() => { });
29
+ },
30
+ configureServer(server) {
31
+ server.middlewares.use((req, res, next) => {
32
+ if (req.url?.endsWith('.wasm')) {
33
+ const wasmPath = path.join(resolvedOptions.cacheDir, path.basename(req.url));
34
+ res.setHeader('Content-Type', 'application/wasm');
35
+ fs.readFile(wasmPath)
36
+ .then(content => res.end(content))
37
+ .catch(() => next());
38
+ }
39
+ else {
40
+ next();
41
+ }
42
+ });
43
+ },
44
+ async load(id) {
45
+ if (!id.endsWith('.cpp'))
46
+ return;
47
+ const result = await compile(id, resolvedOptions, isServe);
48
+ const functions = await extractFunctions(id, result.tsdPath);
49
+ const dtsPath = path.join(path.dirname(id), path.basename(id) + '.d.ts');
50
+ await fs.writeFile(dtsPath, generateDeclarations(functions));
51
+ this.addWatchFile(result.jsPath);
52
+ return generateModule(result.jsPath, functions);
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,4 @@
1
+ import type { CacheManifest } from './types';
2
+ export declare function readManifest(cacheDir: string, readFile: (p: string) => Promise<Buffer | string>): Promise<CacheManifest>;
3
+ export declare function scheduleManifestWrite(cacheDir: string, manifest: CacheManifest, writeFile: (p: string, data: string) => Promise<void>): void;
4
+ export declare function isCacheValid(entry: CacheManifest[string] | undefined, hash: string, stat: (p: string) => Promise<unknown>): Promise<boolean>;
@@ -0,0 +1,25 @@
1
+ import path from 'node:path';
2
+ let manifestWriteChain = Promise.resolve();
3
+ export async function readManifest(cacheDir, readFile) {
4
+ try {
5
+ const raw = await readFile(path.join(cacheDir, 'manifest.json'));
6
+ return JSON.parse(raw.toString());
7
+ }
8
+ catch {
9
+ return {};
10
+ }
11
+ }
12
+ export function scheduleManifestWrite(cacheDir, manifest, writeFile) {
13
+ manifestWriteChain = manifestWriteChain.then(() => writeFile(path.join(cacheDir, 'manifest.json'), JSON.stringify(manifest, null, 2)));
14
+ }
15
+ export async function isCacheValid(entry, hash, stat) {
16
+ if (!entry || entry.hash !== hash)
17
+ return false;
18
+ try {
19
+ await stat(entry.jsPath);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { CppFunction } from './types';
2
+ export declare function generateModule(jsPath: string, functions: CppFunction[]): string;
@@ -0,0 +1,13 @@
1
+ export function generateModule(jsPath, functions) {
2
+ const moduleUrl = jsPath.replace(/\\/g, '/');
3
+ const exports = functions.map(func => {
4
+ const paramNames = func.parameters.map(p => p.name).join(', ');
5
+ return `export function ${func.name}(${paramNames}) { return _mod.${func.name}(${paramNames}); }`;
6
+ }).join('\n');
7
+ return `import createModule from '${moduleUrl}';
8
+
9
+ const _mod = await createModule();
10
+
11
+ ${exports}
12
+ `;
13
+ }
@@ -0,0 +1,7 @@
1
+ import type { CppFunction } from './types';
2
+ export declare const CPP_TO_TS: Record<string, string>;
3
+ export declare function parseTsd(tsdContent: string): CppFunction[];
4
+ export declare function parseRegexFallback(cppSource: string): CppFunction[];
5
+ export declare function extractTopLevelFunctionNames(cppSource: string): string[];
6
+ export declare function buildAutoBindings(names: string[]): string;
7
+ export declare function extractFunctions(id: string, tsdPath: string | null): Promise<CppFunction[]>;
package/dist/parser.js ADDED
@@ -0,0 +1,122 @@
1
+ import { promises as fs } from 'node:fs';
2
+ export const CPP_TO_TS = {
3
+ 'int': 'number',
4
+ 'float': 'number',
5
+ 'double': 'number',
6
+ 'bool': 'boolean',
7
+ 'void': 'void',
8
+ 'char': 'string',
9
+ 'unsigned': 'number',
10
+ 'long': 'number',
11
+ 'short': 'number',
12
+ 'string': 'string',
13
+ 'std::string': 'string',
14
+ 'const string': 'string',
15
+ 'const std::string': 'string',
16
+ 'int32_t': 'number',
17
+ 'int64_t': 'number',
18
+ 'uint32_t': 'number',
19
+ 'uint64_t': 'number',
20
+ 'size_t': 'number',
21
+ 'const char *': 'string',
22
+ 'char *': 'string',
23
+ 'emscripten::val': 'any',
24
+ };
25
+ function mapType(cppType) {
26
+ const normalized = cppType.replace(/\s*[&*]\s*$/, '').trim();
27
+ return CPP_TO_TS[normalized] ?? 'any';
28
+ }
29
+ export function parseTsd(tsdContent) {
30
+ const functions = [];
31
+ const methodRegex = /^\s{2}(\w+)\(([^)]*)\):\s*(\S+);/;
32
+ for (const line of tsdContent.split('\n')) {
33
+ const match = line.match(methodRegex);
34
+ if (!match)
35
+ continue;
36
+ const [, name, rawParams, returnType] = match;
37
+ const parameters = rawParams.trim() === ''
38
+ ? []
39
+ : rawParams.split(',').map(p => {
40
+ const paramMatch = p.trim().match(/^(\w+)\s*:\s*(.+)$/);
41
+ if (!paramMatch)
42
+ return { name: 'p', type: 'any' };
43
+ return { name: paramMatch[1], type: paramMatch[2].trim() };
44
+ });
45
+ functions.push({ name, returnType: returnType.replace(';', ''), parameters });
46
+ }
47
+ return functions;
48
+ }
49
+ export function parseRegexFallback(cppSource) {
50
+ // Strip comments then normalize whitespace for multiline signatures
51
+ const stripped = cppSource
52
+ .replace(/\/\/[^\n]*/g, '')
53
+ .replace(/\/\*[\s\S]*?\*\//g, '')
54
+ .replace(/\n/g, ' ');
55
+ const functionMap = new Map();
56
+ const functionRegex = /(\w[\w\s:]*?)\s+(\w+)\s*\(([^)]*)\)\s*\{/g;
57
+ const bindRegex = /emscripten::function\s*\(\s*"(.*?)"\s*,\s*&(\w+)\s*\)/g;
58
+ let match;
59
+ while ((match = functionRegex.exec(stripped)) !== null) {
60
+ const [, rawReturn, name, rawParams] = match;
61
+ const returnType = mapType(rawReturn.trim());
62
+ const parameters = rawParams.trim() === ''
63
+ ? []
64
+ : rawParams.split(',').map(param => {
65
+ const parts = param.trim().split(/\s+/);
66
+ let type = '';
67
+ let paramName = '';
68
+ if (parts[0] === 'const' && parts.length >= 3) {
69
+ type = mapType(parts.slice(0, 2).join(' '));
70
+ paramName = parts[2]?.replace(/[&*]/g, '') ?? '';
71
+ }
72
+ else {
73
+ type = mapType(parts[0] ?? '');
74
+ paramName = parts[1]?.replace(/[&*]/g, '') ?? '';
75
+ }
76
+ return { type, name: paramName };
77
+ });
78
+ functionMap.set(name, { name, returnType, parameters });
79
+ }
80
+ const functions = [];
81
+ while ((match = bindRegex.exec(stripped)) !== null) {
82
+ const [, exposedName, cppName] = match;
83
+ const func = functionMap.get(cppName);
84
+ if (func)
85
+ functions.push({ ...func, name: exposedName });
86
+ }
87
+ return functions;
88
+ }
89
+ export function extractTopLevelFunctionNames(cppSource) {
90
+ const stripped = cppSource
91
+ .replace(/\/\/[^\n]*/g, '')
92
+ .replace(/\/\*[\s\S]*?\*\//g, '')
93
+ .replace(/\n/g, ' ');
94
+ const names = [];
95
+ const functionRegex = /(\w[\w\s:]*?)\s+(\w+)\s*\(([^)]*)\)\s*\{/g;
96
+ let match;
97
+ while ((match = functionRegex.exec(stripped)) !== null) {
98
+ const [, rawReturn, name] = match;
99
+ if (rawReturn.trim().startsWith('static'))
100
+ continue;
101
+ names.push(name);
102
+ }
103
+ return names;
104
+ }
105
+ export function buildAutoBindings(names) {
106
+ const lines = names.map(n => ` emscripten::function("${n}", &${n});`).join('\n');
107
+ return `\n#include <emscripten/bind.h>\n\nEMSCRIPTEN_BINDINGS(module) {\n${lines}\n}\n`;
108
+ }
109
+ export async function extractFunctions(id, tsdPath) {
110
+ if (tsdPath) {
111
+ try {
112
+ const content = await fs.readFile(tsdPath, 'utf-8');
113
+ if (content.trim().length > 0)
114
+ return parseTsd(content);
115
+ }
116
+ catch {
117
+ // fall through to regex
118
+ }
119
+ }
120
+ const cppSource = await fs.readFile(id, 'utf-8');
121
+ return parseRegexFallback(cppSource);
122
+ }
@@ -0,0 +1,24 @@
1
+ export interface CppFunction {
2
+ name: string;
3
+ returnType: string;
4
+ parameters: Array<{
5
+ type: string;
6
+ name: string;
7
+ }>;
8
+ }
9
+ export interface CppLoaderOptions {
10
+ emccArgs?: string[];
11
+ cacheDir?: string;
12
+ optimizationLevel?: 'O0' | 'O1' | 'O2' | 'O3' | 'Os' | 'Oz';
13
+ /** Auto-generate .vscode/c_cpp_properties.json with Emscripten include paths. Default: true */
14
+ setupVSCode?: boolean;
15
+ }
16
+ export interface CompileResult {
17
+ jsPath: string;
18
+ tsdPath: string;
19
+ }
20
+ export type CacheManifest = Record<string, {
21
+ hash: string;
22
+ jsPath: string;
23
+ tsdPath: string;
24
+ }>;
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function generateCppProperties(projectRoot: string): Promise<void>;
package/dist/vscode.js ADDED
@@ -0,0 +1,44 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+ function getEmscriptenRoot() {
5
+ return new Promise(resolve => {
6
+ const child = spawn('em-config', ['EMSCRIPTEN_ROOT'], {
7
+ stdio: ['ignore', 'pipe', 'ignore'],
8
+ });
9
+ const chunks = [];
10
+ child.stdout?.on('data', (chunk) => chunks.push(chunk));
11
+ child.on('error', () => resolve(null));
12
+ child.on('close', code => {
13
+ resolve(code === 0 ? Buffer.concat(chunks).toString().trim() : null);
14
+ });
15
+ });
16
+ }
17
+ export async function generateCppProperties(projectRoot) {
18
+ const vscodeDir = path.join(projectRoot, '.vscode');
19
+ const outPath = path.join(vscodeDir, 'c_cpp_properties.json');
20
+ try {
21
+ await fs.stat(outPath);
22
+ return;
23
+ }
24
+ catch {
25
+ // file doesn't exist, generate it
26
+ }
27
+ const emRoot = await getEmscriptenRoot();
28
+ if (!emRoot)
29
+ return;
30
+ const includePath = path.join(emRoot, 'system', 'include').replace(/\\/g, '/');
31
+ const config = {
32
+ configurations: [
33
+ {
34
+ name: 'Emscripten',
35
+ includePath: ['${workspaceFolder}/**', `${includePath}/**`],
36
+ cppStandard: 'c++17',
37
+ intelliSenseMode: 'clang-x64',
38
+ },
39
+ ],
40
+ version: 4,
41
+ };
42
+ await fs.mkdir(vscodeDir, { recursive: true });
43
+ await fs.writeFile(outPath, JSON.stringify(config, null, 2) + '\n');
44
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "vite-plugin-cpp-loader",
3
+ "version": "2.0.1",
4
+ "description": "Vite plugin — import .cpp files as typed ES modules compiled to WebAssembly via Emscripten",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "typecheck": "tsc -p tsconfig.test.json",
17
+ "prepublishOnly": "npm run typecheck && npm run build"
18
+ },
19
+ "keywords": [
20
+ "vite",
21
+ "vite-plugin",
22
+ "cpp",
23
+ "c++",
24
+ "webassembly",
25
+ "wasm",
26
+ "emscripten",
27
+ "embind",
28
+ "native",
29
+ "loader",
30
+ "plugin",
31
+ "typescript",
32
+ "wasm-bindgen",
33
+ "cpp-to-wasm"
34
+ ],
35
+ "author": "Franco May",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/francomay3/vite-cpp-loader.git"
40
+ },
41
+ "homepage": "https://github.com/francomay3/vite-cpp-loader#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/francomay3/vite-cpp-loader/issues"
44
+ },
45
+ "peerDependencies": {
46
+ "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.0.0",
50
+ "typescript": "^5.0.0",
51
+ "vite": "^5.0.0",
52
+ "vitest": "^2.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=16.0.0"
56
+ }
57
+ }