1
+ import path from 'path'
2
+ import { pathToFileURL } from 'url'
1
3
import { platform , arch } from 'os'
2
4
import { platformArchTriples } from 'next/dist/compiled/@napi-rs/triples'
3
- import { version as nextVersion , optionalDependencies } from 'next/package.json'
4
5
import * as Log from '../output/log'
5
6
import { getParserOptions } from './options'
6
7
import { eventSwcLoadFailure } from '../../telemetry/events/swc-load-failure'
7
8
import { patchIncorrectLockfile } from '../../lib/patch-incorrect-lockfile'
9
+ import { downloadWasmSwc } from '../../lib/download-wasm-swc'
10
+ import { version as nextVersion } from 'next/package.json'
8
11
9
12
const ArchName = arch ( )
10
13
const PlatformName = platform ( )
11
14
const triples = platformArchTriples [ PlatformName ] [ ArchName ] || [ ]
12
15
13
16
let nativeBindings
14
17
let wasmBindings
18
+ let downloadWasmPromise
19
+ let pendingBindings
15
20
export const lockfilePatchPromise = { }
16
21
17
22
async function loadBindings ( ) {
18
- if ( ! lockfilePatchPromise . cur ) {
19
- // always run lockfile check once so that it gets patched
20
- // even if it doesn't fail to load locally
21
- lockfilePatchPromise . cur = patchIncorrectLockfile ( process . cwd ( ) ) . catch (
22
- console . error
23
- )
23
+ if ( pendingBindings ) {
24
+ return pendingBindings
24
25
}
26
+ pendingBindings = new Promise ( async ( resolve , reject ) => {
27
+ if ( ! lockfilePatchPromise . cur ) {
28
+ // always run lockfile check once so that it gets patched
29
+ // even if it doesn't fail to load locally
30
+ lockfilePatchPromise . cur = patchIncorrectLockfile ( process . cwd ( ) ) . catch (
31
+ console . error
32
+ )
33
+ }
25
34
26
- let attempts = [ ]
27
- try {
28
- return loadNative ( )
29
- } catch ( a ) {
30
- attempts = attempts . concat ( a )
31
- }
35
+ let attempts = [ ]
36
+ try {
37
+ return resolve ( loadNative ( ) )
38
+ } catch ( a ) {
39
+ attempts = attempts . concat ( a )
40
+ }
32
41
33
- // TODO: fetch wasm and fallback when loading native fails
34
- // so that users aren't blocked on this, we still want to
35
- // report the native load failure so we can patch though
36
- try {
37
- let bindings = await loadWasm ( )
38
- return bindings
39
- } catch ( a ) {
40
- attempts = attempts . concat ( a )
41
- }
42
+ try {
43
+ let bindings = await loadWasm ( )
44
+ eventSwcLoadFailure ( { wasm : 'enabled' } )
45
+ return resolve ( bindings )
46
+ } catch ( a ) {
47
+ attempts = attempts . concat ( a )
48
+ }
42
49
43
- logLoadFailure ( attempts )
50
+ try {
51
+ // if not installed already download wasm package on-demand
52
+ // we download to a custom directory instead of to node_modules
53
+ // as node_module import attempts are cached and can't be re-attempted
54
+ // x-ref: https://github.com/nodejs/modules/issues/307
55
+ const wasmDirectory = path . join (
56
+ path . dirname ( require . resolve ( 'next/package.json' ) ) ,
57
+ 'wasm'
58
+ )
59
+ if ( ! downloadWasmPromise ) {
60
+ downloadWasmPromise = downloadWasmSwc ( nextVersion , wasmDirectory )
61
+ }
62
+ await downloadWasmPromise
63
+ let bindings = await loadWasm ( pathToFileURL ( wasmDirectory ) . href )
64
+ eventSwcLoadFailure ( { wasm : 'fallback' } )
65
+
66
+ // still log native load attempts so user is
67
+ // aware it failed and should be fixed
68
+ for ( const attempt of attempts ) {
69
+ Log . warn ( attempt )
70
+ }
71
+ return resolve ( bindings )
72
+ } catch ( a ) {
73
+ attempts = attempts . concat ( a )
74
+ }
75
+
76
+ logLoadFailure ( attempts , true )
77
+ } )
78
+ return pendingBindings
44
79
}
45
80
46
81
function loadBindingsSync ( ) {
@@ -56,47 +91,16 @@ function loadBindingsSync() {
56
91
57
92
let loggingLoadFailure = false
58
93
59
- function logLoadFailure ( attempts ) {
94
+ function logLoadFailure ( attempts , triedWasm = false ) {
60
95
// make sure we only emit the event and log the failure once
61
96
if ( loggingLoadFailure ) return
62
97
loggingLoadFailure = true
63
98
64
99
for ( let attempt of attempts ) {
65
100
Log . warn ( attempt )
66
101
}
67
- let glibcVersion
68
- let installedSwcPackages
69
102
70
- try {
71
- glibcVersion = process . report ?. getReport ( ) . header . glibcVersionRuntime
72
- } catch ( _ ) { }
73
-
74
- try {
75
- const pkgNames = Object . keys ( optionalDependencies || { } ) . filter ( ( pkg ) =>
76
- pkg . startsWith ( '@next/swc' )
77
- )
78
- const installedPkgs = [ ]
79
-
80
- for ( const pkg of pkgNames ) {
81
- try {
82
- const { version } = require ( `${ pkg } /package.json` )
83
- installedPkgs . push ( `${ pkg } @${ version } ` )
84
- } catch ( _ ) { }
85
- }
86
-
87
- if ( installedPkgs . length > 0 ) {
88
- installedSwcPackages = installedPkgs . sort ( ) . join ( ',' )
89
- }
90
- } catch ( _ ) { }
91
-
92
- eventSwcLoadFailure ( {
93
- nextVersion,
94
- glibcVersion,
95
- installedSwcPackages,
96
- arch : process . arch ,
97
- platform : process . platform ,
98
- nodeVersion : process . versions . node ,
99
- } )
103
+ eventSwcLoadFailure ( { wasm : triedWasm ? 'failed' : undefined } )
100
104
. then ( ( ) => lockfilePatchPromise . cur || Promise . resolve ( ) )
101
105
. finally ( ( ) => {
102
106
Log . error (
@@ -106,15 +110,21 @@ function logLoadFailure(attempts) {
106
110
} )
107
111
}
108
112
109
- async function loadWasm ( ) {
113
+ async function loadWasm ( importPath = '' ) {
110
114
if ( wasmBindings ) {
111
115
return wasmBindings
112
116
}
113
117
114
118
let attempts = [ ]
115
119
for ( let pkg of [ '@next/swc-wasm-nodejs' , '@next/swc-wasm-web' ] ) {
116
120
try {
117
- let bindings = await import ( pkg )
121
+ let pkgPath = pkg
122
+
123
+ if ( importPath ) {
124
+ // the import path must be exact when not in node_modules
125
+ pkgPath = path . join ( importPath , pkg , 'wasm.js' )
126
+ }
127
+ let bindings = await import ( pkgPath )
118
128
if ( pkg === '@next/swc-wasm-web' ) {
119
129
bindings = await bindings . default ( )
120
130
}
@@ -139,14 +149,16 @@ async function loadWasm() {
139
149
}
140
150
return wasmBindings
141
151
} catch ( e ) {
142
- // Do not report attempts to load wasm when it is still experimental
143
- // if (e?.code === 'ERR_MODULE_NOT_FOUND') {
144
- // attempts.push(`Attempted to load ${pkg}, but it was not installed`)
145
- // } else {
146
- // attempts.push(
147
- // `Attempted to load ${pkg}, but an error occurred: ${e.message ?? e}`
148
- // )
149
- // }
152
+ // Only log attempts for loading wasm when loading as fallback
153
+ if ( importPath ) {
154
+ if ( e ?. code === 'ERR_MODULE_NOT_FOUND' ) {
155
+ attempts . push ( `Attempted to load ${ pkg } , but it was not installed` )
156
+ } else {
157
+ attempts . push (
158
+ `Attempted to load ${ pkg } , but an error occurred: ${ e . message ?? e } `
159
+ )
160
+ }
161
+ }
150
162
}
151
163
}
152
164
0 commit comments