@@ -43,10 +43,10 @@ export const createRenderRuntimeController = ({
4343 if ( coreRuntime ) return coreRuntime
4444
4545 try {
46- const [ cssBrowser , jsxDom , jsxTranspile ] = await Promise . all ( [
46+ const [ cssBrowser , jsxDom , jsxTransform ] = await Promise . all ( [
4747 importFromCdnWithFallback ( cdnImports . cssBrowser ) ,
4848 importFromCdnWithFallback ( cdnImports . jsxDom ) ,
49- importFromCdnWithFallback ( cdnImports . jsxTranspile ) ,
49+ importFromCdnWithFallback ( cdnImports . jsxTransform ) ,
5050 ] )
5151
5252 if ( typeof cssBrowser . module . cssFromSource !== 'function' ) {
@@ -57,16 +57,16 @@ export const createRenderRuntimeController = ({
5757 throw new Error ( `jsx export was not found from ${ jsxDom . url } ` )
5858 }
5959
60- if ( typeof jsxTranspile . module . transpileJsxSource !== 'function' ) {
60+ if ( typeof jsxTransform . module . transformJsxSource !== 'function' ) {
6161 throw new Error (
62- `transpileJsxSource export was not found from ${ jsxTranspile . url } ` ,
62+ `transformJsxSource export was not found from ${ jsxTransform . url } ` ,
6363 )
6464 }
6565
6666 coreRuntime = {
6767 cssFromSource : cssBrowser . module . cssFromSource ,
6868 jsx : jsxDom . module . jsx ,
69- transpileJsxSource : jsxTranspile . module . transpileJsxSource ,
69+ transformJsxSource : jsxTransform . module . transformJsxSource ,
7070 }
7171
7272 return coreRuntime
@@ -261,7 +261,109 @@ export const createRenderRuntimeController = ({
261261 return React . cloneElement ( value , nextProps )
262262 }
263263
264- const shouldAttemptTranspileFallback = error => error instanceof SyntaxError
264+ const shouldAttemptTranspileFallback = error => {
265+ if ( error instanceof SyntaxError ) {
266+ return true
267+ }
268+
269+ if ( ! ( error instanceof Error ) ) {
270+ return false
271+ }
272+
273+ return / U n e x p e c t e d t o k e n | C a n n o t u s e i m p o r t s t a t e m e n t | U n e x p e c t e d i d e n t i f i e r / . test (
274+ error . message ,
275+ )
276+ }
277+
278+ const isImportRange = range =>
279+ Array . isArray ( range ) &&
280+ range . length === 2 &&
281+ Number . isInteger ( range [ 0 ] ) &&
282+ Number . isInteger ( range [ 1 ] )
283+
284+ const stripImportDeclarations = ( code , imports ) => {
285+ const ranges = imports
286+ . map ( entry => entry ?. range )
287+ . filter ( isImportRange )
288+ . slice ( )
289+ . sort ( ( first , second ) => second [ 0 ] - first [ 0 ] )
290+
291+ let output = code
292+
293+ for ( const [ start , end ] of ranges ) {
294+ if ( start < 0 || end < start || end > output . length ) {
295+ continue
296+ }
297+
298+ output = `${ output . slice ( 0 , start ) } ${ output . slice ( end ) } `
299+ }
300+
301+ return output
302+ }
303+
304+ const buildRuntimeImportPlan = imports => {
305+ const preamble = [ ]
306+ const unsupportedSources = new Set ( )
307+ let requiresReactRuntime = false
308+
309+ for ( const entry of imports ) {
310+ if ( ! entry || entry . importKind !== 'value' ) {
311+ continue
312+ }
313+
314+ if ( entry . source !== 'react' ) {
315+ unsupportedSources . add ( entry . source )
316+ continue
317+ }
318+
319+ requiresReactRuntime = true
320+
321+ for ( const binding of entry . bindings ?? [ ] ) {
322+ if ( ! binding || binding . isTypeOnly ) {
323+ continue
324+ }
325+
326+ if ( binding . kind === 'default' || binding . kind === 'namespace' ) {
327+ preamble . push ( `const ${ binding . local } = React` )
328+ continue
329+ }
330+
331+ if ( binding . kind === 'named' ) {
332+ if ( binding . imported === 'default' ) {
333+ preamble . push ( `const ${ binding . local } = React` )
334+ } else {
335+ preamble . push ( `const ${ binding . local } = React.${ binding . imported } ` )
336+ }
337+ }
338+ }
339+ }
340+
341+ return {
342+ preamble,
343+ requiresReactRuntime,
344+ unsupportedSources : [ ...unsupportedSources ] ,
345+ }
346+ }
347+
348+ const formatTransformDiagnosticsError = diagnostics => {
349+ const firstDiagnostic = diagnostics ?. [ 0 ]
350+
351+ if ( ! firstDiagnostic ) {
352+ return '[jsx] Failed to transform source.'
353+ }
354+
355+ const lines = [ `[jsx] ${ firstDiagnostic . message } ` ]
356+
357+ if ( firstDiagnostic . codeframe ) {
358+ lines . push ( firstDiagnostic . codeframe )
359+ }
360+
361+ if ( firstDiagnostic . helpMessage ) {
362+ lines . push ( firstDiagnostic . helpMessage )
363+ }
364+
365+ return lines . join ( '\n' )
366+ }
265367
266368 const createUserModuleFactory = source =>
267369 new Function (
@@ -464,7 +566,8 @@ export const createRenderRuntimeController = ({
464566 }
465567
466568 const evaluateUserModule = async ( helpers = { } ) => {
467- const { jsx, transpileJsxSource } = await ensureCoreRuntime ( )
569+ const { jsx, transformJsxSource } = await ensureCoreRuntime ( )
570+ let runtimeHelpers = helpers
468571 const userCode = getJsxSource ( )
469572 . replace ( / ^ \s * e x p o r t \s + d e f a u l t \s + f u n c t i o n \b / gm, '__defaultExport = function' )
470573 . replace ( / ^ \s * e x p o r t \s + d e f a u l t \s + c l a s s \b / gm, '__defaultExport = class' )
@@ -482,34 +585,97 @@ export const createRenderRuntimeController = ({
482585 const transpileMode = helpers . React && helpers . reactJsx ? 'react' : 'dom'
483586 const transpileOptionsByMode = {
484587 dom : {
485- sourceType : 'script ' ,
588+ sourceType : 'module ' ,
486589 createElement : 'jsx.createElement' ,
487590 fragment : 'jsx.Fragment' ,
488591 typescript : 'strip' ,
489592 } ,
490593 react : {
491- sourceType : 'script ' ,
594+ sourceType : 'module ' ,
492595 createElement : 'React.createElement' ,
493596 fragment : 'React.Fragment' ,
494597 typescript : 'strip' ,
495598 } ,
496599 }
497- const transpiledUserCode = transpileJsxSource (
600+ const transformedResult = transformJsxSource (
498601 userCode ,
499602 transpileOptionsByMode [ transpileMode ] ,
500- ) . code
501- const moduleFactory = createUserModuleFactory ( transpiledUserCode )
603+ )
604+
605+ if ( transformedResult . diagnostics . length > 0 ) {
606+ throw new Error ( formatTransformDiagnosticsError ( transformedResult . diagnostics ) , {
607+ cause : error ,
608+ } )
609+ }
610+
611+ const importAnalysisResult = transformJsxSource ( transformedResult . code , {
612+ sourceType : 'module' ,
613+ typescript : 'preserve' ,
614+ } )
502615
503- if ( helpers . React && helpers . reactJsx ) {
504- return moduleFactory ( helpers . jsx ?? jsx , helpers . reactJsx , helpers . React )
616+ if ( importAnalysisResult . diagnostics . length > 0 ) {
617+ throw new Error (
618+ formatTransformDiagnosticsError ( importAnalysisResult . diagnostics ) ,
619+ {
620+ cause : error ,
621+ } ,
622+ )
623+ }
624+
625+ const runtimeImportPlan = buildRuntimeImportPlan ( importAnalysisResult . imports )
626+
627+ if ( runtimeImportPlan . unsupportedSources . length > 0 ) {
628+ throw new Error (
629+ `Unsupported runtime imports in playground execution: ${ runtimeImportPlan . unsupportedSources
630+ . map ( specifier => `'${ specifier } '` )
631+ . join ( ', ' ) } .`,
632+ {
633+ cause : error ,
634+ } ,
635+ )
636+ }
637+
638+ if ( runtimeImportPlan . requiresReactRuntime && ! runtimeHelpers . React ) {
639+ const { React, reactJsx } = await ensureReactRuntime ( )
640+ runtimeHelpers = {
641+ ...runtimeHelpers ,
642+ React,
643+ reactJsx : runtimeHelpers . reactJsx ?? reactJsx ,
644+ }
645+ }
646+
647+ const runtimeCode = stripImportDeclarations (
648+ transformedResult . code ,
649+ importAnalysisResult . imports ,
650+ )
651+ const executableUserCode = runtimeImportPlan . preamble . length
652+ ? `${ runtimeImportPlan . preamble . join ( '\n' ) } \n${ runtimeCode } `
653+ : runtimeCode
654+
655+ const moduleFactory = createUserModuleFactory ( executableUserCode )
656+
657+ if ( runtimeHelpers . React && runtimeHelpers . reactJsx ) {
658+ return moduleFactory (
659+ runtimeHelpers . jsx ?? jsx ,
660+ runtimeHelpers . reactJsx ,
661+ runtimeHelpers . React ,
662+ )
505663 }
506664
507665 if ( transpileMode === 'dom' ) {
508- return moduleFactory ( helpers . jsx ?? jsx , helpers . reactJsx , helpers . React )
666+ return moduleFactory (
667+ runtimeHelpers . jsx ?? jsx ,
668+ runtimeHelpers . reactJsx ,
669+ runtimeHelpers . React ,
670+ )
509671 }
510672
511673 const { React, reactJsx } = await ensureReactRuntime ( )
512- return moduleFactory ( helpers . jsx ?? jsx , helpers . reactJsx ?? reactJsx , React )
674+ return moduleFactory (
675+ runtimeHelpers . jsx ?? jsx ,
676+ runtimeHelpers . reactJsx ?? reactJsx ,
677+ React ,
678+ )
513679 }
514680 }
515681
0 commit comments