@ecrindigital/facetpack 0.1.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-present Ecrin Digital
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,141 @@
1
+ # facetpack
2
+
3
+ [![npm version](https://img.shields.io/npm/v/facetpack.svg)](https://www.npmjs.com/package/facetpack)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > High-performance Metro transformer powered by [OXC](https://oxc.rs) and Rust.
7
+
8
+ Replace Babel with a blazing-fast native transformer in your React Native project.
9
+
10
+ Built by [Ecrin Digital](https://ecrin.digital).
11
+
12
+ ## Features
13
+
14
+ - **10-50x Faster** - Native Rust transformer powered by OXC
15
+ - **Drop-in Replacement** - Just wrap your Metro config
16
+ - **TypeScript Support** - Full TypeScript parsing and type stripping
17
+ - **JSX/TSX Support** - Automatic and classic runtime support
18
+ - **Source Maps** - Full source map support for debugging
19
+ - **Zero Config** - Works out of the box with sensible defaults
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install facetpack facetpack-native
25
+ # or
26
+ yarn add facetpack facetpack-native
27
+ # or
28
+ pnpm add facetpack facetpack-native
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ Wrap your Metro configuration with `withFacetpack`:
34
+
35
+ ```javascript
36
+ // metro.config.js
37
+ const { getDefaultConfig } = require('@react-native/metro-config');
38
+ const { withFacetpack } = require('facetpack');
39
+
40
+ const config = getDefaultConfig(__dirname);
41
+
42
+ module.exports = withFacetpack(config);
43
+ ```
44
+
45
+ ### With Options
46
+
47
+ ```javascript
48
+ // metro.config.js
49
+ const { getDefaultConfig } = require('@react-native/metro-config');
50
+ const { withFacetpack } = require('facetpack');
51
+
52
+ const config = getDefaultConfig(__dirname);
53
+
54
+ module.exports = withFacetpack(config, {
55
+ // JSX runtime: 'automatic' (default) or 'classic'
56
+ jsxRuntime: 'automatic',
57
+
58
+ // Import source for automatic runtime
59
+ jsxImportSource: 'react',
60
+
61
+ // Enable TypeScript transformation (default: true)
62
+ typescript: true,
63
+
64
+ // Enable JSX transformation (default: true)
65
+ jsx: true,
66
+ });
67
+ ```
68
+
69
+ ### With Expo
70
+
71
+ ```javascript
72
+ // metro.config.js
73
+ const { getDefaultConfig } = require('expo/metro-config');
74
+ const { withFacetpack } = require('facetpack');
75
+
76
+ const config = getDefaultConfig(__dirname);
77
+
78
+ module.exports = withFacetpack(config);
79
+ ```
80
+
81
+ ## API
82
+
83
+ ### `withFacetpack(config, options?)`
84
+
85
+ Wraps a Metro configuration with the Facetpack transformer.
86
+
87
+ #### Parameters
88
+
89
+ | Parameter | Type | Description |
90
+ |-----------|------|-------------|
91
+ | `config` | `MetroConfig` | Your Metro configuration object |
92
+ | `options` | `FacetpackOptions` | Optional configuration options |
93
+
94
+ #### Options
95
+
96
+ | Option | Type | Default | Description |
97
+ |--------|------|---------|-------------|
98
+ | `jsx` | `boolean` | `true` | Enable JSX transformation |
99
+ | `jsxRuntime` | `'automatic' \| 'classic'` | `'automatic'` | JSX runtime mode |
100
+ | `jsxImportSource` | `string` | `'react'` | Import source for automatic runtime |
101
+ | `jsxPragma` | `string` | `'React.createElement'` | Pragma for classic runtime |
102
+ | `jsxPragmaFrag` | `string` | `'React.Fragment'` | Fragment pragma for classic runtime |
103
+ | `typescript` | `boolean` | `true` | Strip TypeScript types |
104
+ | `sourceExts` | `string[]` | `['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs']` | File extensions to transform |
105
+
106
+ ## How It Works
107
+
108
+ Facetpack replaces Metro's default Babel transformer with a native Rust transformer powered by [OXC](https://oxc.rs). This provides significant performance improvements:
109
+
110
+ | Operation | vs Babel |
111
+ |-----------|----------|
112
+ | Parse | ~50x faster |
113
+ | Transform | ~20x faster |
114
+
115
+ The transformer handles:
116
+ - TypeScript → JavaScript (type stripping)
117
+ - JSX → JavaScript (createElement or jsx-runtime)
118
+ - Source map generation
119
+
120
+ ## Requirements
121
+
122
+ - React Native 0.73+
123
+ - Metro 0.80+
124
+ - Node.js 18+
125
+
126
+ ## Compatibility
127
+
128
+ Facetpack is designed to be a drop-in replacement for Babel in most React Native projects. However, if you rely on specific Babel plugins or transforms, you may need to keep Babel for those files.
129
+
130
+ ## License
131
+
132
+ MIT - see [LICENSE](./LICENSE) for details.
133
+
134
+ ## Credits
135
+
136
+ - [OXC](https://oxc.rs) - The high-performance JavaScript toolchain
137
+ - [NAPI-RS](https://napi.rs) - Rust bindings for Node.js
138
+
139
+ ---
140
+
141
+ Made with Rust by [Ecrin Digital](https://ecrin.digital)
@@ -0,0 +1,8 @@
1
+ export declare function setCachedResolutions(originModulePath: string, resolutions: Map<string, string | null>): void;
2
+ export declare function getCachedResolution(originModulePath: string, specifier: string): string | null | undefined;
3
+ export declare function clearCache(): void;
4
+ export declare function getCacheStats(): {
5
+ files: number;
6
+ resolutions: number;
7
+ };
8
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AASA,wBAAgB,oBAAoB,CAClC,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACtC,IAAI,CASN;AAED,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,GAAG,SAAS,CAa3B;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,aAAa,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAMtE"}
package/dist/index.cjs ADDED
@@ -0,0 +1,330 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+
29
+ // src/index.ts
30
+ var exports_src = {};
31
+ __export(exports_src, {
32
+ withFacetpack: () => withFacetpack,
33
+ transform: () => transform,
34
+ setTransformerOptions: () => setTransformerOptions,
35
+ resolveSync: () => import_facetpack_native3.resolveSync,
36
+ getStoredOptions: () => getStoredOptions,
37
+ getCacheStats: () => getCacheStats,
38
+ createTransformer: () => createTransformer,
39
+ createResolver: () => createResolver,
40
+ clearCache: () => clearCache
41
+ });
42
+ module.exports = __toCommonJS(exports_src);
43
+
44
+ // src/withFacetpack.ts
45
+ var import_facetpack_native = require("@ecrindigital/facetpack-native");
46
+
47
+ // src/cache.ts
48
+ var resolutionCache = new Map;
49
+ var CACHE_TTL = 30000;
50
+ function setCachedResolutions(originModulePath, resolutions) {
51
+ const now = Date.now();
52
+ const cached = new Map;
53
+ for (const [specifier, path] of resolutions) {
54
+ cached.set(specifier, { path, timestamp: now });
55
+ }
56
+ resolutionCache.set(originModulePath, cached);
57
+ }
58
+ function getCachedResolution(originModulePath, specifier) {
59
+ const fileCache = resolutionCache.get(originModulePath);
60
+ if (!fileCache)
61
+ return;
62
+ const cached = fileCache.get(specifier);
63
+ if (!cached)
64
+ return;
65
+ if (Date.now() - cached.timestamp > CACHE_TTL) {
66
+ fileCache.delete(specifier);
67
+ return;
68
+ }
69
+ return cached.path;
70
+ }
71
+ function clearCache() {
72
+ resolutionCache.clear();
73
+ }
74
+ function getCacheStats() {
75
+ let resolutions = 0;
76
+ for (const fileCache of resolutionCache.values()) {
77
+ resolutions += fileCache.size;
78
+ }
79
+ return { files: resolutionCache.size, resolutions };
80
+ }
81
+
82
+ // src/withFacetpack.ts
83
+ var import_path = require("path");
84
+ var import_url = require("url");
85
+ var DEFAULT_SOURCE_EXTS = ["ts", "tsx", "js", "jsx", "mjs", "cjs"];
86
+ var __filename2 = import_url.fileURLToPath("file:///Users/alexischangridel/Projects/ecrindigital/facetpack/packages/facetpack/src/withFacetpack.ts");
87
+ var __dirname2 = import_path.dirname(__filename2);
88
+ function withFacetpack(config, options = {}) {
89
+ const sourceExts = options.sourceExts ?? DEFAULT_SOURCE_EXTS;
90
+ const transformerPath = import_path.join(__dirname2, "transformer.js");
91
+ storeTransformerOptions(options);
92
+ return {
93
+ ...config,
94
+ transformer: {
95
+ ...config.transformer,
96
+ babelTransformerPath: transformerPath,
97
+ getTransformOptions: async (entryPoints, opts, getDepsOf) => {
98
+ const baseOptions = await config.transformer?.getTransformOptions?.(entryPoints, opts, getDepsOf);
99
+ return {
100
+ ...baseOptions,
101
+ transform: {
102
+ ...baseOptions?.transform,
103
+ experimentalImportSupport: true,
104
+ inlineRequires: true
105
+ }
106
+ };
107
+ }
108
+ },
109
+ resolver: {
110
+ ...config.resolver,
111
+ sourceExts: [
112
+ ...new Set([
113
+ ...config.resolver?.sourceExts ?? [],
114
+ ...sourceExts
115
+ ])
116
+ ],
117
+ resolveRequest: (context, moduleName, platform) => {
118
+ if (context.originModulePath.includes("node_modules")) {
119
+ return context.resolveRequest(context, moduleName, platform);
120
+ }
121
+ const cached = getCachedResolution(context.originModulePath, moduleName);
122
+ if (cached !== undefined) {
123
+ if (cached) {
124
+ return { type: "sourceFile", filePath: cached };
125
+ }
126
+ return context.resolveRequest(context, moduleName, platform);
127
+ }
128
+ const directory = context.originModulePath.substring(0, context.originModulePath.lastIndexOf("/"));
129
+ const result = import_facetpack_native.resolveSync(directory, moduleName, {
130
+ extensions: [...sourceExts.map((ext) => `.${ext}`), ".json"],
131
+ mainFields: ["react-native", "browser", "main"],
132
+ conditionNames: ["react-native", "import", "require"]
133
+ });
134
+ if (result.path) {
135
+ return { type: "sourceFile", filePath: result.path };
136
+ }
137
+ return context.resolveRequest(context, moduleName, platform);
138
+ }
139
+ }
140
+ };
141
+ }
142
+ function storeTransformerOptions(options) {
143
+ process.env.FACETPACK_OPTIONS = JSON.stringify(options);
144
+ }
145
+ function getStoredOptions() {
146
+ try {
147
+ const optionsJson = process.env.FACETPACK_OPTIONS;
148
+ if (optionsJson) {
149
+ return JSON.parse(optionsJson);
150
+ }
151
+ } catch {}
152
+ return {};
153
+ }
154
+ // src/transformer.ts
155
+ var import_facetpack_native2 = require("@ecrindigital/facetpack-native");
156
+ var import_parser = require("@babel/parser");
157
+ var IMPORT_REGEX = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
158
+ var REQUIRE_REGEX = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
159
+ function extractSpecifiers(code) {
160
+ const specifiers = new Set;
161
+ let match;
162
+ while ((match = IMPORT_REGEX.exec(code)) !== null) {
163
+ if (match[1])
164
+ specifiers.add(match[1]);
165
+ }
166
+ while ((match = REQUIRE_REGEX.exec(code)) !== null) {
167
+ if (match[1])
168
+ specifiers.add(match[1]);
169
+ }
170
+ return Array.from(specifiers);
171
+ }
172
+ function preResolveImports(filename, code, sourceExts) {
173
+ const specifiers = extractSpecifiers(code);
174
+ if (specifiers.length === 0)
175
+ return;
176
+ const directory = filename.substring(0, filename.lastIndexOf("/"));
177
+ const results = import_facetpack_native2.resolveBatchSync(directory, specifiers, {
178
+ extensions: [...sourceExts.map((ext) => `.${ext}`), ".json"],
179
+ mainFields: ["react-native", "browser", "main"],
180
+ conditionNames: ["react-native", "import", "require"]
181
+ });
182
+ const resolutions = new Map;
183
+ for (let i = 0;i < specifiers.length; i++) {
184
+ const specifier = specifiers[i];
185
+ if (specifier) {
186
+ resolutions.set(specifier, results[i]?.path ?? null);
187
+ }
188
+ }
189
+ setCachedResolutions(filename, resolutions);
190
+ }
191
+ var defaultOptions = {
192
+ jsx: true,
193
+ jsxRuntime: "automatic",
194
+ jsxImportSource: "react",
195
+ jsxPragma: "React.createElement",
196
+ jsxPragmaFrag: "React.Fragment",
197
+ typescript: true,
198
+ sourceExts: ["ts", "tsx", "js", "jsx", "mjs", "cjs"]
199
+ };
200
+ var globalOptions = {};
201
+ var fallbackTransformer = null;
202
+ function getFallbackTransformer() {
203
+ if (fallbackTransformer) {
204
+ return fallbackTransformer;
205
+ }
206
+ const transformerPaths = [
207
+ "@expo/metro-config/babel-transformer",
208
+ "@react-native/metro-babel-transformer",
209
+ "metro-react-native-babel-transformer"
210
+ ];
211
+ for (const transformerPath of transformerPaths) {
212
+ try {
213
+ fallbackTransformer = require(transformerPath);
214
+ return fallbackTransformer;
215
+ } catch {}
216
+ }
217
+ fallbackTransformer = {
218
+ transform: ({ src }) => ({ code: src, map: null })
219
+ };
220
+ return fallbackTransformer;
221
+ }
222
+ function setTransformerOptions(options) {
223
+ globalOptions = options;
224
+ }
225
+ function getOptions() {
226
+ return { ...defaultOptions, ...globalOptions };
227
+ }
228
+ function isNodeModules(filename) {
229
+ return filename.includes("node_modules");
230
+ }
231
+ function shouldTransform(filename, options) {
232
+ if (isNodeModules(filename)) {
233
+ return false;
234
+ }
235
+ const ext = filename.split(".").pop()?.toLowerCase();
236
+ if (!ext)
237
+ return false;
238
+ return options.sourceExts.includes(ext);
239
+ }
240
+ function transform(params) {
241
+ const { filename, src, options: metroOptions } = params;
242
+ const opts = getOptions();
243
+ if (process.env.FACETPACK_DEBUG) {
244
+ console.log(`[Facetpack] Processing: ${filename}`);
245
+ }
246
+ if (!shouldTransform(filename, opts)) {
247
+ if (process.env.FACETPACK_DEBUG) {
248
+ console.log(`[Facetpack] Fallback: ${filename}`);
249
+ }
250
+ return getFallbackTransformer().transform(params);
251
+ }
252
+ if (process.env.FACETPACK_DEBUG) {
253
+ console.log(`[Facetpack] OXC Transform: ${filename}`);
254
+ }
255
+ try {
256
+ const isClassic = opts.jsxRuntime === "classic";
257
+ const result = import_facetpack_native2.transformSync(filename, src, {
258
+ jsx: opts.jsx,
259
+ jsxRuntime: isClassic ? import_facetpack_native2.JsxRuntime.Classic : import_facetpack_native2.JsxRuntime.Automatic,
260
+ ...isClassic ? { jsxPragma: opts.jsxPragma, jsxPragmaFrag: opts.jsxPragmaFrag } : { jsxImportSource: opts.jsxImportSource },
261
+ typescript: opts.typescript,
262
+ sourcemap: metroOptions.dev
263
+ });
264
+ if (result.errors.length > 0) {
265
+ const errorMessage = result.errors.join(`
266
+ `);
267
+ throw new Error(`Facetpack transform error in ${filename}:
268
+ ${errorMessage}`);
269
+ }
270
+ preResolveImports(filename, result.code, opts.sourceExts);
271
+ const ast = import_parser.parse(result.code, {
272
+ sourceType: "unambiguous",
273
+ plugins: ["jsx"]
274
+ });
275
+ const output = {
276
+ ast,
277
+ code: result.code,
278
+ map: result.map ? JSON.parse(result.map) : null
279
+ };
280
+ if (process.env.FACETPACK_DEBUG) {
281
+ console.log(`[Facetpack] Output for ${filename}:`);
282
+ console.log(result.code.slice(0, 500));
283
+ }
284
+ return output;
285
+ } catch (error) {
286
+ if (error instanceof Error) {
287
+ error.message = `[Facetpack] ${error.message}`;
288
+ }
289
+ throw error;
290
+ }
291
+ }
292
+ function createTransformer(options = {}) {
293
+ const opts = { ...defaultOptions, ...options };
294
+ return {
295
+ transform(params) {
296
+ const { filename, src, options: metroOptions } = params;
297
+ if (!shouldTransform(filename, opts)) {
298
+ return getFallbackTransformer().transform(params);
299
+ }
300
+ const isClassic = opts.jsxRuntime === "classic";
301
+ const result = import_facetpack_native2.transformSync(filename, src, {
302
+ jsx: opts.jsx,
303
+ jsxRuntime: isClassic ? import_facetpack_native2.JsxRuntime.Classic : import_facetpack_native2.JsxRuntime.Automatic,
304
+ ...isClassic ? { jsxPragma: opts.jsxPragma, jsxPragmaFrag: opts.jsxPragmaFrag } : { jsxImportSource: opts.jsxImportSource },
305
+ typescript: opts.typescript,
306
+ sourcemap: metroOptions.dev
307
+ });
308
+ if (result.errors.length > 0) {
309
+ throw new Error(`Facetpack transform error in ${filename}:
310
+ ${result.errors.join(`
311
+ `)}`);
312
+ }
313
+ return {
314
+ code: result.code,
315
+ map: result.map ? JSON.parse(result.map) : null
316
+ };
317
+ }
318
+ };
319
+ }
320
+ // src/resolver.ts
321
+ var import_facetpack_native3 = require("@ecrindigital/facetpack-native");
322
+ function createResolver(options) {
323
+ return {
324
+ resolve(originModulePath, moduleName) {
325
+ const directory = originModulePath.substring(0, originModulePath.lastIndexOf("/"));
326
+ const result = import_facetpack_native3.resolveSync(directory, moduleName, options);
327
+ return result.path ?? null;
328
+ }
329
+ };
330
+ }
@@ -0,0 +1,6 @@
1
+ export { withFacetpack, getStoredOptions } from './withFacetpack';
2
+ export { transform, createTransformer, setTransformerOptions } from './transformer';
3
+ export { createResolver, resolveSync } from './resolver';
4
+ export { clearCache, getCacheStats } from './cache';
5
+ export type { FacetpackOptions, MetroConfig, TransformParams, TransformOptions, TransformResult, } from './types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AACnF,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACnD,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,SAAS,CAAA"}