1
1
/**
2
+ * @typedef {import('estree-jsx').CallExpression } CallExpression
2
3
* @typedef {import('estree-jsx').Directive } Directive
3
4
* @typedef {import('estree-jsx').ExportAllDeclaration } ExportAllDeclaration
4
5
* @typedef {import('estree-jsx').ExportDefaultDeclaration } ExportDefaultDeclaration
5
6
* @typedef {import('estree-jsx').ExportNamedDeclaration } ExportNamedDeclaration
6
7
* @typedef {import('estree-jsx').ExportSpecifier } ExportSpecifier
7
8
* @typedef {import('estree-jsx').Expression } Expression
8
9
* @typedef {import('estree-jsx').FunctionDeclaration } FunctionDeclaration
10
+ * @typedef {import('estree-jsx').Identifier } Identifier
9
11
* @typedef {import('estree-jsx').ImportDeclaration } ImportDeclaration
10
12
* @typedef {import('estree-jsx').ImportDefaultSpecifier } ImportDefaultSpecifier
11
13
* @typedef {import('estree-jsx').ImportExpression } ImportExpression
@@ -36,6 +38,7 @@ import {create} from '../util/estree-util-create.js'
36
38
import { declarationToExpression } from '../util/estree-util-declaration-to-expression.js'
37
39
import { isDeclaration } from '../util/estree-util-is-declaration.js'
38
40
import { specifiersToDeclarations } from '../util/estree-util-specifiers-to-declarations.js'
41
+ import { toIdOrMemberExpression } from '../util/estree-util-to-id-or-member-expression.js'
39
42
40
43
/**
41
44
* Wrap the estree in `MDXContent`.
@@ -46,8 +49,8 @@ import {specifiersToDeclarations} from '../util/estree-util-specifiers-to-declar
46
49
* Transform.
47
50
*/
48
51
export 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
51
54
const outputFormat = options . outputFormat || 'program'
52
55
const pragma =
53
56
options . pragma === undefined ? 'React.createElement' : options . pragma
@@ -321,9 +324,68 @@ export function recmaDocument(options) {
321
324
322
325
tree . body = replacement
323
326
324
- if ( baseUrl ) {
327
+ let usesImportMetaUrlVariable = false
328
+ let usesResolveDynamicHelper = false
329
+
330
+ if ( baseHref || outputFormat === 'function-body' ) {
325
331
walk ( tree , {
326
332
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
+
327
389
if (
328
390
node . type === 'MemberExpression' &&
329
391
'object' in node &&
@@ -333,14 +395,38 @@ export function recmaDocument(options) {
333
395
node . object . property . name === 'meta' &&
334
396
node . property . name === 'url'
335
397
) {
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 )
338
402
this . replace ( replacement )
339
403
}
340
404
}
341
405
} )
342
406
}
343
407
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
+
344
430
/**
345
431
* @param {ExportAllDeclaration | ExportNamedDeclaration } node
346
432
* Export node.
@@ -379,32 +465,6 @@ export function recmaDocument(options) {
379
465
* Nothing.
380
466
*/
381
467
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
-
408
468
/** @type {ModuleDeclaration | Statement | undefined } */
409
469
let replace
410
470
/** @type {Expression } */
@@ -639,3 +699,175 @@ export function recmaDocument(options) {
639
699
]
640
700
}
641
701
}
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