11/**
2+ * @typedef {import('estree-jsx').CallExpression } CallExpression
23 * @typedef {import('estree-jsx').Directive } Directive
34 * @typedef {import('estree-jsx').ExportAllDeclaration } ExportAllDeclaration
45 * @typedef {import('estree-jsx').ExportDefaultDeclaration } ExportDefaultDeclaration
56 * @typedef {import('estree-jsx').ExportNamedDeclaration } ExportNamedDeclaration
67 * @typedef {import('estree-jsx').ExportSpecifier } ExportSpecifier
78 * @typedef {import('estree-jsx').Expression } Expression
89 * @typedef {import('estree-jsx').FunctionDeclaration } FunctionDeclaration
10+ * @typedef {import('estree-jsx').Identifier } Identifier
911 * @typedef {import('estree-jsx').ImportDeclaration } ImportDeclaration
1012 * @typedef {import('estree-jsx').ImportDefaultSpecifier } ImportDefaultSpecifier
1113 * @typedef {import('estree-jsx').ImportExpression } ImportExpression
@@ -36,6 +38,7 @@ import {create} from '../util/estree-util-create.js'
3638import { declarationToExpression } from '../util/estree-util-declaration-to-expression.js'
3739import { isDeclaration } from '../util/estree-util-is-declaration.js'
3840import { specifiersToDeclarations } from '../util/estree-util-specifiers-to-declarations.js'
41+ import { toIdOrMemberExpression } from '../util/estree-util-to-id-or-member-expression.js'
3942
4043/**
4144 * Wrap the estree in `MDXContent`.
@@ -46,8 +49,8 @@ import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declar
4649 * Transform.
4750 */
4851export function recmaDocument ( options ) {
49- const baseUrl_ = options . baseUrl || undefined
50- const baseUrl = typeof baseUrl_ === 'object' ? baseUrl_ . href : baseUrl_
52+ const baseUrl = options . baseUrl || undefined
53+ const baseHref = typeof baseUrl === 'object' ? baseUrl . href : baseUrl
5154 const outputFormat = options . outputFormat || 'program'
5255 const pragma =
5356 options . pragma === undefined ? 'React.createElement' : options . pragma
@@ -321,9 +324,68 @@ export function recmaDocument(options) {
321324
322325 tree . body = replacement
323326
324- if ( baseUrl ) {
327+ let usesImportMetaUrlVariable = false
328+ let usesResolveDynamicHelper = false
329+
330+ if ( baseHref || outputFormat === 'function-body' ) {
325331 walk ( tree , {
326332 enter ( node ) {
333+ if (
334+ ( node . type === 'ExportAllDeclaration' ||
335+ node . type === 'ExportNamedDeclaration' ||
336+ node . type === 'ImportDeclaration' ) &&
337+ node . source
338+ ) {
339+ // We never hit this branch when generating function bodies, as
340+ // statements are already compiled away into import expressions.
341+ assert ( baseHref , 'unexpected missing `baseHref` in branch' )
342+
343+ let value = node . source . value
344+ // The literal source for statements can only be string.
345+ assert ( typeof value === 'string' , 'expected string source' )
346+
347+ // Resolve a specifier.
348+ // This is the same as `_resolveDynamicMdxSpecifier`, which has to
349+ // be injected to work with expressions at runtime, but as we have
350+ // `baseHref` at compile time here and statements are static
351+ // strings, we can do it now.
352+ try {
353+ // To do: use `URL.canParse` next major.
354+ // eslint-disable-next-line no-new
355+ new URL ( value )
356+ // Fine: a full URL.
357+ } catch {
358+ if (
359+ value . startsWith ( '/' ) ||
360+ value . startsWith ( './' ) ||
361+ value . startsWith ( '../' )
362+ ) {
363+ value = new URL ( value , baseHref ) . href
364+ } else {
365+ // Fine: are bare specifier.
366+ }
367+ }
368+
369+ /** @type {SimpleLiteral } */
370+ const replacement = { type : 'Literal' , value}
371+ create ( node . source , replacement )
372+ node . source = replacement
373+ return
374+ }
375+
376+ if ( node . type === 'ImportExpression' ) {
377+ usesResolveDynamicHelper = true
378+ /** @type {CallExpression } */
379+ const replacement = {
380+ type : 'CallExpression' ,
381+ callee : { type : 'Identifier' , name : '_resolveDynamicMdxSpecifier' } ,
382+ arguments : [ node . source ] ,
383+ optional : false
384+ }
385+ node . source = replacement
386+ return
387+ }
388+
327389 if (
328390 node . type === 'MemberExpression' &&
329391 'object' in node &&
@@ -333,14 +395,38 @@ export function recmaDocument(options) {
333395 node . object . property . name === 'meta' &&
334396 node . property . name === 'url'
335397 ) {
336- /** @type {SimpleLiteral } */
337- const replacement = { type : 'Literal' , value : baseUrl }
398+ usesImportMetaUrlVariable = true
399+ /** @type {Identifier } */
400+ const replacement = { type : 'Identifier' , name : '_importMetaUrl' }
401+ create ( node , replacement )
338402 this . replace ( replacement )
339403 }
340404 }
341405 } )
342406 }
343407
408+ if ( usesResolveDynamicHelper ) {
409+ if ( ! baseHref ) {
410+ usesImportMetaUrlVariable = true
411+ }
412+
413+ tree . body . push (
414+ resolveDynamicMdxSpecifier (
415+ baseHref
416+ ? { type : 'Literal' , value : baseHref }
417+ : { type : 'Identifier' , name : '_importMetaUrl' }
418+ )
419+ )
420+ }
421+
422+ if ( usesImportMetaUrlVariable ) {
423+ assert (
424+ outputFormat === 'function-body' ,
425+ 'expected `function-body` when using dynamic url injection'
426+ )
427+ tree . body . unshift ( ...createImportMetaUrlVariable ( ) )
428+ }
429+
344430 /**
345431 * @param {ExportAllDeclaration | ExportNamedDeclaration } node
346432 * Export node.
@@ -379,32 +465,6 @@ export function recmaDocument(options) {
379465 * Nothing.
380466 */
381467 function handleEsm ( node ) {
382- // Rewrite the source of the `import` / `export … from`.
383- // See: <https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier>
384- if ( baseUrl && node . source ) {
385- let value = String ( node . source . value )
386-
387- try {
388- // A full valid URL.
389- value = String ( new URL ( value ) )
390- } catch {
391- // Relative: `/example.js`, `./example.js`, and `../example.js`.
392- if ( / ^ \. { 0 , 2 } \/ / . test ( value ) ) {
393- value = String ( new URL ( value , baseUrl ) )
394- }
395- // Otherwise, it’s a bare specifiers.
396- // For example `some-package`, `@some-package`, and
397- // `some-package/path`.
398- // These are supported in Node and browsers plan to support them
399- // with import maps (<https://github.com/WICG/import-maps>).
400- }
401-
402- /** @type {Literal } */
403- const literal = { type : 'Literal' , value}
404- create ( node . source , literal )
405- node . source = literal
406- }
407-
408468 /** @type {ModuleDeclaration | Statement | undefined } */
409469 let replace
410470 /** @type {Expression } */
@@ -639,3 +699,175 @@ export function recmaDocument(options) {
639699 ]
640700 }
641701}
702+
703+ /**
704+ * @param {Expression } importMetaUrl
705+ * @returns {FunctionDeclaration }
706+ */
707+ function resolveDynamicMdxSpecifier ( importMetaUrl ) {
708+ return {
709+ type : 'FunctionDeclaration' ,
710+ id : { type : 'Identifier' , name : '_resolveDynamicMdxSpecifier' } ,
711+ generator : false ,
712+ async : false ,
713+ params : [ { type : 'Identifier' , name : 'd' } ] ,
714+ body : {
715+ type : 'BlockStatement' ,
716+ body : [
717+ {
718+ type : 'IfStatement' ,
719+ test : {
720+ type : 'BinaryExpression' ,
721+ left : {
722+ type : 'UnaryExpression' ,
723+ operator : 'typeof' ,
724+ prefix : true ,
725+ argument : { type : 'Identifier' , name : 'd' }
726+ } ,
727+ operator : '!==' ,
728+ right : { type : 'Literal' , value : 'string' }
729+ } ,
730+ consequent : {
731+ type : 'ReturnStatement' ,
732+ argument : { type : 'Identifier' , name : 'd' }
733+ } ,
734+ alternate : null
735+ } ,
736+ // To do: use `URL.canParse` when widely supported (see commented
737+ // out code below).
738+ {
739+ type : 'TryStatement' ,
740+ block : {
741+ type : 'BlockStatement' ,
742+ body : [
743+ {
744+ type : 'ExpressionStatement' ,
745+ expression : {
746+ type : 'NewExpression' ,
747+ callee : { type : 'Identifier' , name : 'URL' } ,
748+ arguments : [ { type : 'Identifier' , name : 'd' } ]
749+ }
750+ } ,
751+ {
752+ type : 'ReturnStatement' ,
753+ argument : { type : 'Identifier' , name : 'd' }
754+ }
755+ ]
756+ } ,
757+ handler : {
758+ type : 'CatchClause' ,
759+ param : null ,
760+ body : { type : 'BlockStatement' , body : [ ] }
761+ } ,
762+ finalizer : null
763+ } ,
764+ // To do: use `URL.canParse` when widely supported.
765+ // {
766+ // type: 'IfStatement',
767+ // test: {
768+ // type: 'CallExpression',
769+ // callee: toIdOrMemberExpression(['URL', 'canParse']),
770+ // arguments: [{type: 'Identifier', name: 'd'}],
771+ // optional: false
772+ // },
773+ // consequent: {
774+ // type: 'ReturnStatement',
775+ // argument: {type: 'Identifier', name: 'd'}
776+ // },
777+ // alternate: null
778+ // },
779+ {
780+ type : 'IfStatement' ,
781+ test : {
782+ type : 'LogicalExpression' ,
783+ left : {
784+ type : 'LogicalExpression' ,
785+ left : {
786+ type : 'CallExpression' ,
787+ callee : toIdOrMemberExpression ( [ 'd' , 'startsWith' ] ) ,
788+ arguments : [ { type : 'Literal' , value : '/' } ] ,
789+ optional : false
790+ } ,
791+ operator : '||' ,
792+ right : {
793+ type : 'CallExpression' ,
794+ callee : toIdOrMemberExpression ( [ 'd' , 'startsWith' ] ) ,
795+ arguments : [ { type : 'Literal' , value : './' } ] ,
796+ optional : false
797+ }
798+ } ,
799+ operator : '||' ,
800+ right : {
801+ type : 'CallExpression' ,
802+ callee : toIdOrMemberExpression ( [ 'd' , 'startsWith' ] ) ,
803+ arguments : [ { type : 'Literal' , value : '../' } ] ,
804+ optional : false
805+ }
806+ } ,
807+ consequent : {
808+ type : 'ReturnStatement' ,
809+ argument : {
810+ type : 'MemberExpression' ,
811+ object : {
812+ type : 'NewExpression' ,
813+ callee : { type : 'Identifier' , name : 'URL' } ,
814+ arguments : [ { type : 'Identifier' , name : 'd' } , importMetaUrl ]
815+ } ,
816+ property : { type : 'Identifier' , name : 'href' } ,
817+ computed : false ,
818+ optional : false
819+ }
820+ } ,
821+ alternate : null
822+ } ,
823+ {
824+ type : 'ReturnStatement' ,
825+ argument : { type : 'Identifier' , name : 'd' }
826+ }
827+ ]
828+ }
829+ }
830+ }
831+
832+ /**
833+ * @returns {Array<Statement> }
834+ */
835+ function createImportMetaUrlVariable ( ) {
836+ return [
837+ {
838+ type : 'VariableDeclaration' ,
839+ declarations : [
840+ {
841+ type : 'VariableDeclarator' ,
842+ id : { type : 'Identifier' , name : '_importMetaUrl' } ,
843+ init : toIdOrMemberExpression ( [ 'arguments' , 0 , 'baseUrl' ] )
844+ }
845+ ] ,
846+ kind : 'const'
847+ } ,
848+ {
849+ type : 'IfStatement' ,
850+ test : {
851+ type : 'UnaryExpression' ,
852+ operator : '!' ,
853+ prefix : true ,
854+ argument : { type : 'Identifier' , name : '_importMetaUrl' }
855+ } ,
856+ consequent : {
857+ type : 'ThrowStatement' ,
858+ argument : {
859+ type : 'NewExpression' ,
860+ callee : { type : 'Identifier' , name : 'Error' } ,
861+ arguments : [
862+ {
863+ type : 'Literal' ,
864+ value :
865+ 'Unexpected missing `options.baseUrl` needed to support `export … from`, `import`, or `import.meta.url` when generating `function-body`'
866+ }
867+ ]
868+ }
869+ } ,
870+ alternate : null
871+ }
872+ ]
873+ }
0 commit comments