2
2
Actions specific to the esbuild bundler
3
3
"""
4
4
import logging
5
+ from tempfile import NamedTemporaryFile
6
+
5
7
from pathlib import Path
6
8
7
9
from aws_lambda_builders .actions import BaseAction , Purpose , ActionFailedError
@@ -23,7 +25,16 @@ class EsbuildBundleAction(BaseAction):
23
25
24
26
ENTRY_POINTS = "entry_points"
25
27
26
- def __init__ (self , scratch_dir , artifacts_dir , bundler_config , osutils , subprocess_esbuild ):
28
+ def __init__ (
29
+ self ,
30
+ scratch_dir ,
31
+ artifacts_dir ,
32
+ bundler_config ,
33
+ osutils ,
34
+ subprocess_esbuild ,
35
+ subprocess_nodejs = None ,
36
+ skip_deps = False ,
37
+ ):
27
38
"""
28
39
:type scratch_dir: str
29
40
:param scratch_dir: an existing (writable) directory for temporary files
@@ -35,15 +46,23 @@ def __init__(self, scratch_dir, artifacts_dir, bundler_config, osutils, subproce
35
46
:type osutils: aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils
36
47
:param osutils: An instance of OS Utilities for file manipulation
37
48
38
- :type subprocess_esbuild: aws_lambda_builders.workflows.nodejs_npm.npm .SubprocessEsbuild
49
+ :type subprocess_esbuild: aws_lambda_builders.workflows.nodejs_npm_esbuild.esbuild .SubprocessEsbuild
39
50
:param subprocess_esbuild: An instance of the Esbuild process wrapper
51
+
52
+ :type subprocess_nodejs: aws_lambda_builders.workflows.nodejs_npm_esbuild.node.SubprocessNodejs
53
+ :param subprocess_nodejs: An instance of the nodejs process wrapper
54
+
55
+ :type skip_deps: bool
56
+ :param skip_deps: if dependencies should be omitted from bundling
40
57
"""
41
58
super (EsbuildBundleAction , self ).__init__ ()
42
59
self .scratch_dir = scratch_dir
43
60
self .artifacts_dir = artifacts_dir
44
61
self .bundler_config = bundler_config
45
62
self .osutils = osutils
46
63
self .subprocess_esbuild = subprocess_esbuild
64
+ self .skip_deps = skip_deps
65
+ self .subprocess_nodejs = subprocess_nodejs
47
66
48
67
def execute (self ):
49
68
"""
@@ -81,11 +100,73 @@ def execute(self):
81
100
args .append ("--sourcemap" )
82
101
args .append ("--target={}" .format (target ))
83
102
args .append ("--outdir={}" .format (self .artifacts_dir ))
103
+
104
+ if self .skip_deps :
105
+ LOG .info ("Running custom esbuild using Node.js" )
106
+ script = EsbuildBundleAction ._get_node_esbuild_template (
107
+ explicit_entry_points , target , self .artifacts_dir , minify , sourcemap
108
+ )
109
+ self ._run_external_esbuild_in_nodejs (script )
110
+ return
111
+
84
112
try :
85
113
self .subprocess_esbuild .run (args , cwd = self .scratch_dir )
86
114
except EsbuildExecutionError as ex :
87
115
raise ActionFailedError (str (ex ))
88
116
117
+ def _run_external_esbuild_in_nodejs (self , script ):
118
+ """
119
+ Run esbuild in a separate process through Node.js
120
+ Workaround for https://github.com/evanw/esbuild/issues/1958
121
+
122
+ :type script: str
123
+ :param script: Node.js script to execute
124
+
125
+ :raises lambda_builders.actions.ActionFailedError: when esbuild packaging fails
126
+ """
127
+ with NamedTemporaryFile (dir = self .scratch_dir , mode = "w" ) as tmp :
128
+ tmp .write (script )
129
+ tmp .flush ()
130
+ try :
131
+ self .subprocess_nodejs .run ([tmp .name ], cwd = self .scratch_dir )
132
+ except EsbuildExecutionError as ex :
133
+ raise ActionFailedError (str (ex ))
134
+
135
+ @staticmethod
136
+ def _get_node_esbuild_template (entry_points , target , out_dir , minify , sourcemap ):
137
+ """
138
+ Get the esbuild nodejs plugin template
139
+
140
+ :type entry_points: List[str]
141
+ :param entry_points: list of entry points
142
+
143
+ :type target: str
144
+ :param target: target version
145
+
146
+ :type out_dir: str
147
+ :param out_dir: output directory to bundle into
148
+
149
+ :type minify: bool
150
+ :param minify: if bundled code should be minified
151
+
152
+ :type sourcemap: bool
153
+ :param sourcemap: if esbuild should produce a sourcemap
154
+
155
+ :rtype: str
156
+ :return: formatted template
157
+ """
158
+ curr_dir = Path (__file__ ).resolve ().parent
159
+ with open (str (Path (curr_dir , "esbuild-plugin.js.template" )), "r" ) as f :
160
+ input_str = f .read ()
161
+ result = input_str .format (
162
+ target = target ,
163
+ minify = "true" if minify else "false" ,
164
+ sourcemap = "true" if sourcemap else "false" ,
165
+ out_dir = repr (out_dir ),
166
+ entry_points = entry_points ,
167
+ )
168
+ return result
169
+
89
170
def _get_explicit_file_type (self , entry_point , entry_path ):
90
171
"""
91
172
Get an entry point with an explicit .ts or .js suffix.
@@ -112,3 +193,67 @@ def _get_explicit_file_type(self, entry_point, entry_path):
112
193
return entry_point + ext
113
194
114
195
raise ActionFailedError ("entry point {} does not exist" .format (entry_path ))
196
+
197
+
198
+ class EsbuildCheckVersionAction (BaseAction ):
199
+ """
200
+ A Lambda Builder Action that verifies that esbuild is a version supported by sam accelerate
201
+ """
202
+
203
+ NAME = "EsbuildCheckVersion"
204
+ DESCRIPTION = "Checking esbuild version"
205
+ PURPOSE = Purpose .COMPILE_SOURCE
206
+
207
+ MIN_VERSION = "0.14.13"
208
+
209
+ def __init__ (self , scratch_dir , subprocess_esbuild ):
210
+ """
211
+ :type scratch_dir: str
212
+ :param scratch_dir: temporary directory where esbuild is executed
213
+
214
+ :type subprocess_esbuild: aws_lambda_builders.workflows.nodejs_npm_esbuild.esbuild.SubprocessEsbuild
215
+ :param subprocess_esbuild: An instance of the Esbuild process wrapper
216
+ """
217
+ super ().__init__ ()
218
+ self .scratch_dir = scratch_dir
219
+ self .subprocess_esbuild = subprocess_esbuild
220
+
221
+ def execute (self ):
222
+ """
223
+ Runs the action.
224
+
225
+ :raises lambda_builders.actions.ActionFailedError: when esbuild version checking fails
226
+ """
227
+ args = ["--version" ]
228
+
229
+ try :
230
+ version = self .subprocess_esbuild .run (args , cwd = self .scratch_dir )
231
+ except EsbuildExecutionError as ex :
232
+ raise ActionFailedError (str (ex ))
233
+
234
+ LOG .debug ("Found esbuild with version: %s" , version )
235
+
236
+ try :
237
+ check_version = EsbuildCheckVersionAction ._get_version_tuple (self .MIN_VERSION )
238
+ esbuild_version = EsbuildCheckVersionAction ._get_version_tuple (version )
239
+
240
+ if esbuild_version < check_version :
241
+ raise ActionFailedError (
242
+ f"Unsupported esbuild version. To use a dependency layer, the esbuild version must be at "
243
+ f"least { self .MIN_VERSION } . Version found: { version } "
244
+ )
245
+ except (TypeError , ValueError ) as ex :
246
+ raise ActionFailedError (f"Unable to parse esbuild version: { str (ex )} " )
247
+
248
+ @staticmethod
249
+ def _get_version_tuple (version_string ):
250
+ """
251
+ Get an integer tuple representation of the version for comparison
252
+
253
+ :type version_string: str
254
+ :param version_string: string containing the esbuild version
255
+
256
+ :rtype: tuple
257
+ :return: version tuple used for comparison
258
+ """
259
+ return tuple (map (int , version_string .split ("." )))
0 commit comments