@knighted/jsx 1.6.2 → 1.6.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.
@@ -224,6 +224,8 @@ const TEMPLATE_PARSER_OPTIONS = {
224
224
  };
225
225
  const DEFAULT_TAGS = ['jsx', 'reactJsx'];
226
226
  const DEFAULT_MODE = 'runtime';
227
+ const WEB_TARGETS = new Set(['web', 'webworker', 'electron-renderer', 'node-webkit']);
228
+ const isWebTarget = (target) => target ? WEB_TARGETS.has(target) : false;
227
229
  const HELPER_SNIPPETS = {
228
230
  react: `const __jsxReactMergeProps = (...sources) => Object.assign({}, ...sources)
229
231
  const __jsxReact = (type, props, ...children) => React.createElement(type, props, ...children)
@@ -671,7 +673,14 @@ const transformSource = (source, config, options) => {
671
673
  .filter(Boolean)
672
674
  .join('\n');
673
675
  if (helperSource) {
674
- magic.append(`\n${helperSource}`);
676
+ const helperBlock = `${helperSource.trimEnd()}\n\n`;
677
+ const shebangIndex = source.startsWith('#!') ? source.indexOf('\n') : -1;
678
+ if (shebangIndex >= 0) {
679
+ magic.appendLeft(shebangIndex + 1, helperBlock);
680
+ }
681
+ else {
682
+ magic.prepend(helperBlock);
683
+ }
675
684
  mutated = true;
676
685
  }
677
686
  const code = mutated ? magic.toString() : source;
@@ -692,6 +701,8 @@ function jsxLoader(input) {
692
701
  const callback = this.async();
693
702
  try {
694
703
  const options = this.getOptions?.() ?? {};
704
+ const warn = this.emitWarning?.bind(this);
705
+ const webTarget = isWebTarget(this.target);
695
706
  const explicitTags = Array.isArray(options.tags)
696
707
  ? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
697
708
  : null;
@@ -705,6 +716,9 @@ function jsxLoader(input) {
705
716
  const configuredTagModes = options.tagModes && typeof options.tagModes === 'object'
706
717
  ? options.tagModes
707
718
  : undefined;
719
+ const userSpecifiedMode = parseLoaderMode(options.mode);
720
+ const defaultMode = userSpecifiedMode ?? DEFAULT_MODE;
721
+ const userConfiguredTags = new Set();
708
722
  if (configuredTagModes) {
709
723
  Object.entries(configuredTagModes).forEach(([tagName, mode]) => {
710
724
  const parsed = parseLoaderMode(mode);
@@ -712,15 +726,26 @@ function jsxLoader(input) {
712
726
  return;
713
727
  }
714
728
  tagModes.set(tagName, parsed);
729
+ userConfiguredTags.add(tagName);
715
730
  });
716
731
  }
717
- const defaultMode = parseLoaderMode(options.mode) ?? DEFAULT_MODE;
718
732
  const tags = Array.from(new Set([...tagList, ...tagModes.keys()]));
719
733
  tags.forEach(tagName => {
720
734
  if (!tagModes.has(tagName)) {
721
735
  tagModes.set(tagName, defaultMode);
722
736
  }
723
737
  });
738
+ /**
739
+ * If targeting the web and runtime mode is only implied (not explicitly requested),
740
+ * keep the runtime output but surface a warning so users can opt into react mode when
741
+ * bundling for the browser.
742
+ */
743
+ if (webTarget && userSpecifiedMode === null) {
744
+ const hasImplicitRuntime = tags.some(tagName => tagModes.get(tagName) === 'runtime' && !userConfiguredTags.has(tagName));
745
+ if (hasImplicitRuntime) {
746
+ warn?.(new Error('[jsx-loader] Web target detected while defaulting to runtime mode; the shipped parser expects a Node-like environment. Set mode: "react" (or configure per-tag) when bundling client code, or provide a browser-safe runtime parser if you intentionally need runtime output.'));
747
+ }
748
+ }
724
749
  const source = typeof input === 'string' ? input : input.toString('utf8');
725
750
  const enableSourceMap = options.sourceMap === true;
726
751
  const { code, map } = transformSource(source, {
@@ -2,6 +2,8 @@ import { type SourceMap } from 'magic-string';
2
2
  type LoaderCallback = (error: Error | null, content?: string, map?: SourceMap | null) => void;
3
3
  type LoaderContext<TOptions> = {
4
4
  resourcePath: string;
5
+ target?: string;
6
+ emitWarning?: (warning: Error | string) => void;
5
7
  async(): LoaderCallback;
6
8
  getOptions?: () => Partial<TOptions>;
7
9
  };
@@ -1,5 +1,8 @@
1
- import { createRequire } from 'node:module';
2
- const nodeRequire = createRequire(require("node:url").pathToFileURL(__filename).href);
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.__setNodeRequireForTesting = exports.ensureNodeDom = void 0;
4
+ const node_module_1 = require("node:module");
5
+ const nodeRequire = (0, node_module_1.createRequire)(require("node:url").pathToFileURL(__filename).href);
3
6
  let requireOverride = null;
4
7
  const resolveRequire = () => requireOverride ?? nodeRequire;
5
8
  const DOM_TEMPLATE = '<!doctype html><html><body></body></html>';
@@ -70,7 +73,7 @@ const createShimWindow = () => {
70
73
  throw new AggregateError(errors, help);
71
74
  };
72
75
  let bootstrapped = false;
73
- export const ensureNodeDom = () => {
76
+ const ensureNodeDom = () => {
74
77
  if (hasDom() || bootstrapped) {
75
78
  return;
76
79
  }
@@ -78,6 +81,8 @@ export const ensureNodeDom = () => {
78
81
  assignGlobalTargets(windowObj);
79
82
  bootstrapped = true;
80
83
  };
81
- export const __setNodeRequireForTesting = (mockRequire) => {
84
+ exports.ensureNodeDom = ensureNodeDom;
85
+ const __setNodeRequireForTesting = (mockRequire) => {
82
86
  requireOverride = mockRequire;
83
87
  };
88
+ exports.__setNodeRequireForTesting = __setNodeRequireForTesting;
@@ -1,6 +1,9 @@
1
- import { enableJsxDebugDiagnostics } from '../../debug/diagnostics.cjs';
2
- import { ensureNodeDom } from '../bootstrap.cjs';
3
- import { jsx as baseJsx } from '../../jsx.cjs';
4
- enableJsxDebugDiagnostics({ mode: 'always' });
5
- ensureNodeDom();
6
- export const jsx = baseJsx;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsx = void 0;
4
+ const diagnostics_js_1 = require("../../debug/diagnostics.cjs");
5
+ const bootstrap_js_1 = require("../bootstrap.cjs");
6
+ const jsx_js_1 = require("../../jsx.cjs");
7
+ (0, diagnostics_js_1.enableJsxDebugDiagnostics)({ mode: 'always' });
8
+ (0, bootstrap_js_1.ensureNodeDom)();
9
+ exports.jsx = jsx_js_1.jsx;
@@ -1,4 +1,7 @@
1
- import { ensureNodeDom } from './bootstrap.cjs';
2
- import { jsx as baseJsx } from '../jsx.cjs';
3
- ensureNodeDom();
4
- export const jsx = baseJsx;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsx = void 0;
4
+ const bootstrap_js_1 = require("./bootstrap.cjs");
5
+ const jsx_js_1 = require("../jsx.cjs");
6
+ (0, bootstrap_js_1.ensureNodeDom)();
7
+ exports.jsx = jsx_js_1.jsx;
@@ -1 +1,5 @@
1
- export { reactJsx } from '../../react/react-jsx.cjs';
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reactJsx = void 0;
4
+ var react_jsx_js_1 = require("../../react/react-jsx.cjs");
5
+ Object.defineProperty(exports, "reactJsx", { enumerable: true, get: function () { return react_jsx_js_1.reactJsx; } });
@@ -2,6 +2,8 @@ import { type SourceMap } from 'magic-string';
2
2
  type LoaderCallback = (error: Error | null, content?: string, map?: SourceMap | null) => void;
3
3
  type LoaderContext<TOptions> = {
4
4
  resourcePath: string;
5
+ target?: string;
6
+ emitWarning?: (warning: Error | string) => void;
5
7
  async(): LoaderCallback;
6
8
  getOptions?: () => Partial<TOptions>;
7
9
  };
@@ -218,6 +218,8 @@ const TEMPLATE_PARSER_OPTIONS = {
218
218
  };
219
219
  const DEFAULT_TAGS = ['jsx', 'reactJsx'];
220
220
  const DEFAULT_MODE = 'runtime';
221
+ const WEB_TARGETS = new Set(['web', 'webworker', 'electron-renderer', 'node-webkit']);
222
+ const isWebTarget = (target) => target ? WEB_TARGETS.has(target) : false;
221
223
  const HELPER_SNIPPETS = {
222
224
  react: `const __jsxReactMergeProps = (...sources) => Object.assign({}, ...sources)
223
225
  const __jsxReact = (type, props, ...children) => React.createElement(type, props, ...children)
@@ -665,7 +667,14 @@ const transformSource = (source, config, options) => {
665
667
  .filter(Boolean)
666
668
  .join('\n');
667
669
  if (helperSource) {
668
- magic.append(`\n${helperSource}`);
670
+ const helperBlock = `${helperSource.trimEnd()}\n\n`;
671
+ const shebangIndex = source.startsWith('#!') ? source.indexOf('\n') : -1;
672
+ if (shebangIndex >= 0) {
673
+ magic.appendLeft(shebangIndex + 1, helperBlock);
674
+ }
675
+ else {
676
+ magic.prepend(helperBlock);
677
+ }
669
678
  mutated = true;
670
679
  }
671
680
  const code = mutated ? magic.toString() : source;
@@ -686,6 +695,8 @@ export default function jsxLoader(input) {
686
695
  const callback = this.async();
687
696
  try {
688
697
  const options = this.getOptions?.() ?? {};
698
+ const warn = this.emitWarning?.bind(this);
699
+ const webTarget = isWebTarget(this.target);
689
700
  const explicitTags = Array.isArray(options.tags)
690
701
  ? options.tags.filter((value) => typeof value === 'string' && value.length > 0)
691
702
  : null;
@@ -699,6 +710,9 @@ export default function jsxLoader(input) {
699
710
  const configuredTagModes = options.tagModes && typeof options.tagModes === 'object'
700
711
  ? options.tagModes
701
712
  : undefined;
713
+ const userSpecifiedMode = parseLoaderMode(options.mode);
714
+ const defaultMode = userSpecifiedMode ?? DEFAULT_MODE;
715
+ const userConfiguredTags = new Set();
702
716
  if (configuredTagModes) {
703
717
  Object.entries(configuredTagModes).forEach(([tagName, mode]) => {
704
718
  const parsed = parseLoaderMode(mode);
@@ -706,15 +720,26 @@ export default function jsxLoader(input) {
706
720
  return;
707
721
  }
708
722
  tagModes.set(tagName, parsed);
723
+ userConfiguredTags.add(tagName);
709
724
  });
710
725
  }
711
- const defaultMode = parseLoaderMode(options.mode) ?? DEFAULT_MODE;
712
726
  const tags = Array.from(new Set([...tagList, ...tagModes.keys()]));
713
727
  tags.forEach(tagName => {
714
728
  if (!tagModes.has(tagName)) {
715
729
  tagModes.set(tagName, defaultMode);
716
730
  }
717
731
  });
732
+ /**
733
+ * If targeting the web and runtime mode is only implied (not explicitly requested),
734
+ * keep the runtime output but surface a warning so users can opt into react mode when
735
+ * bundling for the browser.
736
+ */
737
+ if (webTarget && userSpecifiedMode === null) {
738
+ const hasImplicitRuntime = tags.some(tagName => tagModes.get(tagName) === 'runtime' && !userConfiguredTags.has(tagName));
739
+ if (hasImplicitRuntime) {
740
+ warn?.(new Error('[jsx-loader] Web target detected while defaulting to runtime mode; the shipped parser expects a Node-like environment. Set mode: "react" (or configure per-tag) when bundling client code, or provide a browser-safe runtime parser if you intentionally need runtime output.'));
741
+ }
742
+ }
718
743
  const source = typeof input === 'string' ? input : input.toString('utf8');
719
744
  const enableSourceMap = options.sourceMap === true;
720
745
  const { code, map } = transformSource(source, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/jsx",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Runtime JSX tagged template that renders DOM or React trees anywhere without a build step.",
5
5
  "keywords": [
6
6
  "jsx runtime",
@@ -112,19 +112,23 @@
112
112
  },
113
113
  "engineStrict": true,
114
114
  "scripts": {
115
- "build": "duel && npm run build:lite && npm run build:cli",
115
+ "build": "duel --mode globals && npm run build:lite && npm run build:cli",
116
116
  "prepare": "husky",
117
117
  "precheck-types": "npm run build",
118
118
  "check-types": "npm run check-types:lib && npm run check-types:demo && npm run check-types:test",
119
119
  "check-types:lib": "tsc --noEmit --project tsconfig.json",
120
120
  "check-types:demo": "tsc --noEmit --project examples/browser/tsconfig.json",
121
121
  "check-types:test": "tsc --noEmit --project tsconfig.vitest.json",
122
+ "clean:deps": "rimraf node_modules",
123
+ "clean:dist": "rimraf dist",
124
+ "clean": "npm run clean:dist && npm run clean:deps",
122
125
  "lint": "eslint src test",
126
+ "pretest": "npm run build",
123
127
  "cycles": "madge src --circular --extensions ts,tsx,js,jsx --ts-config tsconfig.json",
124
128
  "prettier": "prettier -w .",
125
129
  "prettier:check": "prettier --check .",
126
- "test": "KNIGHTED_JSX_CLI_TEST=1 vitest run --coverage",
127
- "test:watch": "KNIGHTED_JSX_CLI_TEST=1 vitest",
130
+ "test": "cross-env KNIGHTED_JSX_CLI_TEST=1 vitest run --coverage",
131
+ "test:watch": "cross-env KNIGHTED_JSX_CLI_TEST=1 vitest",
128
132
  "test:e2e": "npm run build && npm run setup:wasm && npm run build:fixture && playwright test",
129
133
  "build:fixture": "node scripts/build-rspack-fixture.mjs",
130
134
  "demo:node-ssr": "node test/fixtures/node-ssr/render.mjs",
@@ -139,7 +143,7 @@
139
143
  },
140
144
  "devDependencies": {
141
145
  "@eslint/js": "^9.39.1",
142
- "@knighted/duel": "^3.2.0",
146
+ "@knighted/duel": "^4.0.0",
143
147
  "@oxc-project/types": "^0.105.0",
144
148
  "@playwright/test": "^1.57.0",
145
149
  "@rspack/core": "^1.0.5",
@@ -149,6 +153,7 @@
149
153
  "@types/react-dom": "^19.2.3",
150
154
  "@vitest/coverage-v8": "^4.0.14",
151
155
  "@vitest/eslint-plugin": "^1.6.4",
156
+ "cross-env": "^10.1.0",
152
157
  "eslint": "^9.39.1",
153
158
  "eslint-plugin-n": "^17.10.3",
154
159
  "eslint-plugin-playwright": "^2.4.0",
@@ -163,6 +168,7 @@
163
168
  "prettier": "^3.7.3",
164
169
  "react": "^19.0.0",
165
170
  "react-dom": "^19.0.0",
171
+ "rimraf": "^6.1.2",
166
172
  "tsup": "^8.5.1",
167
173
  "typescript": "^5.9.3",
168
174
  "typescript-eslint": "^8.48.0",