@knighted/jsx 1.6.3-rc.1 → 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
  };
@@ -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.3-rc.1",
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",
@@ -119,9 +119,9 @@
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:deps && npm run clean:dist",
122
+ "clean:deps": "rimraf node_modules",
123
+ "clean:dist": "rimraf dist",
124
+ "clean": "npm run clean:dist && npm run clean:deps",
125
125
  "lint": "eslint src test",
126
126
  "pretest": "npm run build",
127
127
  "cycles": "madge src --circular --extensions ts,tsx,js,jsx --ts-config tsconfig.json",
@@ -143,7 +143,7 @@
143
143
  },
144
144
  "devDependencies": {
145
145
  "@eslint/js": "^9.39.1",
146
- "@knighted/duel": "^4.0.0-rc.5",
146
+ "@knighted/duel": "^4.0.0",
147
147
  "@oxc-project/types": "^0.105.0",
148
148
  "@playwright/test": "^1.57.0",
149
149
  "@rspack/core": "^1.0.5",