@keak/sdk 2.0.2 → 2.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keak/sdk",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Production-ready A/B testing and experimentation SDK for React applications with visual editing, source mapping, and real-time variant testing",
5
5
  "author": "Keak Team",
6
6
  "homepage": "https://www.keak.com/",
@@ -14,10 +14,13 @@ function withKeak(nextConfig = {}) {
14
14
  return {
15
15
  ...nextConfig,
16
16
  webpack(config, options) {
17
- const { dev } = options;
17
+ const { dev, isServer } = options;
18
+
19
+ console.log('[Keak Plugin] webpack called - dev:', dev, 'isServer:', isServer);
18
20
 
19
21
  // Only inject loader in development mode
20
22
  if (!dev) {
23
+ console.log('[Keak Plugin] Skipping - not in development mode');
21
24
  // Call original webpack config if it exists
22
25
  if (typeof nextConfig.webpack === 'function') {
23
26
  return nextConfig.webpack(config, options);
@@ -30,6 +33,9 @@ function withKeak(nextConfig = {}) {
30
33
  const keakLoaderPath = fs.existsSync(projectKeakLoader)
31
34
  ? projectKeakLoader
32
35
  : path.resolve(__dirname, 'webpack-loader-babel/index.js');
36
+
37
+ console.log('[Keak Plugin] Using loader path:', keakLoaderPath);
38
+ console.log('[Keak Plugin] Loader exists:', fs.existsSync(keakLoaderPath));
33
39
 
34
40
  // Ensure config.module.rules exists
35
41
  if (!config.module || !config.module.rules) {
@@ -1,16 +1,15 @@
1
1
  /**
2
- * Webpack loader that uses Babel to inject Keak source attributes into JSX
3
- * Simplified version - runs BEFORE SWC compilation
2
+ * Webpack loader that injects Keak source attributes into JSX
3
+ *
4
+ * Uses regex-based injection instead of full Babel AST transformation
5
+ * to avoid interfering with React's module resolution in Next.js 15
6
+ *
4
7
  * Only runs in development mode
5
8
  */
6
9
 
7
10
  const path = require('path');
8
- const babelParser = require('@babel/parser');
9
- const babelTraverse = require('@babel/traverse').default;
10
- const babelGenerator = require('@babel/generator').default;
11
- const t = require('@babel/types');
12
11
 
13
- module.exports = function keakLoaderBabel(source) {
12
+ module.exports = function keakSourceLoader(source) {
14
13
  this.cacheable(true);
15
14
 
16
15
  const resourcePath = this.resourcePath;
@@ -22,101 +21,87 @@ module.exports = function keakLoaderBabel(source) {
22
21
  }
23
22
 
24
23
  // Skip if not a JSX/TSX file
25
- if (!/\.(jsx|tsx|js|ts)$/.test(resourcePath)) {
24
+ if (!/\.(jsx|tsx)$/.test(resourcePath)) {
26
25
  return source;
27
26
  }
28
27
 
29
- try {
30
- // Parse the source code
31
- const ast = babelParser.parse(source, {
32
- sourceType: 'module',
33
- plugins: [
34
- 'jsx',
35
- 'typescript',
36
- 'decorators-legacy',
37
- 'classProperties',
38
- 'objectRestSpread',
39
- 'dynamicImport',
40
- 'nullishCoalescingOperator',
41
- 'optionalChaining'
42
- ]
43
- });
28
+ // Skip node_modules
29
+ if (resourcePath.includes('node_modules')) {
30
+ return source;
31
+ }
32
+
33
+ // Skip files that use next/font (known to conflict with Babel processing)
34
+ if (source.includes('next/font') || source.includes('@next/font')) {
35
+ return source;
36
+ }
44
37
 
45
- let skipFile = false;
38
+ // Skip if already processed
39
+ if (source.includes('data-keak-src=')) {
40
+ return source;
41
+ }
46
42
 
47
- // Check for next/font imports (Babel/SWC conflict)
48
- babelTraverse(ast, {
49
- ImportDeclaration(importPath) {
50
- const source = importPath.node.source.value;
51
- if (source.includes('next/font') || source.includes('@next/font')) {
52
- skipFile = true;
53
- importPath.stop();
43
+ try {
44
+ // Smart relative path detection
45
+ let sourceFile;
46
+ const srcParts = resourcePath.split('/src/');
47
+ if (srcParts.length > 1) {
48
+ sourceFile = 'src/' + srcParts[srcParts.length - 1];
49
+ } else {
50
+ const appParts = resourcePath.split('/app/');
51
+ if (appParts.length > 1) {
52
+ sourceFile = 'app/' + appParts[appParts.length - 1];
53
+ } else {
54
+ const compParts = resourcePath.split('/components/');
55
+ if (compParts.length > 1) {
56
+ sourceFile = 'components/' + compParts[compParts.length - 1];
57
+ } else {
58
+ sourceFile = path.relative(process.cwd(), resourcePath);
54
59
  }
55
60
  }
56
- });
57
-
58
- if (skipFile) {
59
- return source;
60
61
  }
61
62
 
62
- // Transform JSX elements
63
- babelTraverse(ast, {
64
- JSXOpeningElement(nodePath) {
65
- const node = nodePath.node;
66
-
67
- // Skip if already has keak attributes
68
- const hasKeakAttr = node.attributes.some(attr =>
69
- t.isJSXAttribute(attr) &&
70
- t.isJSXIdentifier(attr.name) &&
71
- (attr.name.name === 'data-keak-file' || attr.name.name === 'data-keak-src')
72
- );
73
-
74
- if (hasKeakAttr || !node.loc) {
75
- return;
76
- }
77
-
78
- // Smart relative path detection
79
- let sourceFile;
80
- const srcParts = resourcePath.split('/src/');
81
- if (srcParts.length > 1) {
82
- sourceFile = 'src/' + srcParts[srcParts.length - 1];
83
- } else {
84
- const appParts = resourcePath.split('/app/');
85
- if (appParts.length > 1) {
86
- sourceFile = 'app/' + appParts[appParts.length - 1];
63
+ // Track the current line number
64
+ const lines = source.split('\n');
65
+ const processedLines = [];
66
+
67
+ for (let i = 0; i < lines.length; i++) {
68
+ const line = lines[i];
69
+ const lineNumber = i + 1;
70
+
71
+ // Match JSX opening tags: <ComponentName or <div, etc.
72
+ // But not self-closing tags that are already complete, or fragments
73
+ const processedLine = line.replace(
74
+ // Match: <TagName followed by space, newline, or >
75
+ // But NOT: </tag, <>, <Fragment
76
+ /<([A-Z][a-zA-Z0-9]*|[a-z][a-zA-Z0-9-]*)(\s|>)/g,
77
+ (match, tagName, after) => {
78
+ // Skip fragments and closing tags
79
+ if (tagName === 'Fragment' || tagName === '') {
80
+ return match;
81
+ }
82
+
83
+ // Skip if this looks like it already has data-keak attributes
84
+ if (line.includes('data-keak-')) {
85
+ return match;
86
+ }
87
+
88
+ // Inject the attribute
89
+ const attr = ` data-keak-src="${sourceFile}:${lineNumber}:1"`;
90
+
91
+ if (after === '>') {
92
+ // <tag> -> <tag data-keak-src="...">
93
+ return `<${tagName}${attr}>`;
87
94
  } else {
88
- const compParts = resourcePath.split('/components/');
89
- if (compParts.length > 1) {
90
- sourceFile = 'components/' + compParts[compParts.length - 1];
91
- } else {
92
- sourceFile = path.relative(process.cwd(), resourcePath);
93
- }
95
+ // <tag -> <tag data-keak-src="..."
96
+ return `<${tagName}${attr}${after}`;
94
97
  }
95
98
  }
99
+ );
100
+
101
+ processedLines.push(processedLine);
102
+ }
96
103
 
97
- const { line } = node.loc.start;
98
-
99
- // Add data-keak-file and data-keak-line attributes
100
- node.attributes.push(
101
- t.jsxAttribute(
102
- t.jsxIdentifier('data-keak-file'),
103
- t.stringLiteral(sourceFile)
104
- ),
105
- t.jsxAttribute(
106
- t.jsxIdentifier('data-keak-line'),
107
- t.stringLiteral(String(line))
108
- )
109
- );
110
- }
111
- });
112
-
113
- // Generate transformed code
114
- const output = babelGenerator(ast, {
115
- retainLines: false,
116
- compact: false
117
- }, source);
118
-
119
- return output.code;
104
+ return processedLines.join('\n');
120
105
  } catch (error) {
121
106
  // Return original source on error
122
107
  console.error(`[keak-loader] Error processing ${resourcePath}:`, error.message);