2021-03-21 06:13:01 +01:00
"""
esbuild rule
"""
load ( " @build_bazel_rules_nodejs//:providers.bzl " , " JSEcmaScriptModuleInfo " , " JSModuleInfo " , " NpmPackageInfo " , " node_modules_aspect " )
load ( " @build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl " , " MODULE_MAPPINGS_ASPECT_RESULTS_NAME " , " module_mappings_aspect " )
2021-03-28 01:46:59 +01:00
load ( " :helpers.bzl " , " filter_files " , " generate_path_mapping " , " resolve_js_input " , " write_jsconfig_file " )
2021-04-07 07:19:23 +02:00
load ( " :toolchain.bzl " , " TOOLCHAIN " )
2021-03-21 06:13:01 +01:00
def _esbuild_impl ( ctx ) :
# For each dep, JSEcmaScriptModuleInfo is used if found, then JSModuleInfo and finally
# the DefaultInfo files are used if the former providers are not found.
deps_depsets = [ ]
# Path alias mapings are used to create a jsconfig with mappings so that esbuild
# how to resolve custom package or module names
path_alias_mappings = dict ( )
npm_workspaces = [ ]
if ( ctx . attr . link_workspace_root ) :
path_alias_mappings . update ( generate_path_mapping ( ctx . workspace_name , " . " ) )
for dep in ctx . attr . deps :
if JSEcmaScriptModuleInfo in dep :
deps_depsets . append ( dep [ JSEcmaScriptModuleInfo ] . sources )
if JSModuleInfo in dep :
deps_depsets . append ( dep [ JSModuleInfo ] . sources )
elif hasattr ( dep , " files " ) :
deps_depsets . append ( dep . files )
2021-04-07 06:53:53 +02:00
if DefaultInfo in dep :
deps_depsets . append ( dep [ DefaultInfo ] . data_runfiles . files )
2021-03-21 06:13:01 +01:00
if NpmPackageInfo in dep :
deps_depsets . append ( dep [ NpmPackageInfo ] . sources )
npm_workspaces . append ( dep [ NpmPackageInfo ] . workspace )
# Collect the path alias mapping to resolve packages correctly
if hasattr ( dep , MODULE_MAPPINGS_ASPECT_RESULTS_NAME ) :
for key , value in getattr ( dep , MODULE_MAPPINGS_ASPECT_RESULTS_NAME ) . items ( ) :
path_alias_mappings . update ( generate_path_mapping ( key , value [ 1 ] . replace ( ctx . bin_dir . path + " / " , " " ) ) )
node_modules_mappings = [
" ../../../external/ %s /node_modules/* " % workspace
for workspace in depset ( npm_workspaces ) . to_list ( )
]
path_alias_mappings . update ( { " * " : node_modules_mappings } )
deps_inputs = depset ( transitive = deps_depsets ) . to_list ( )
inputs = filter_files ( ctx . files . entry_point , [ " .mjs " , " .js " ] ) + ctx . files . srcs + deps_inputs
metafile = ctx . actions . declare_file ( " %s _metadata.json " % ctx . attr . name )
outputs = [ metafile ]
entry_point = resolve_js_input ( ctx . file . entry_point , inputs )
args = ctx . actions . args ( )
args . add ( " --bundle " , entry_point . path )
2021-04-07 06:53:53 +02:00
if len ( ctx . attr . sourcemap ) > 0 :
args . add_joined ( [ " --sourcemap " , ctx . attr . sourcemap ] , join_with = " = " )
else :
args . add ( " --sourcemap " )
2021-03-21 06:13:01 +01:00
args . add ( " --preserve-symlinks " )
args . add_joined ( [ " --platform " , ctx . attr . platform ] , join_with = " = " )
args . add_joined ( [ " --target " , ctx . attr . target ] , join_with = " = " )
args . add_joined ( [ " --log-level " , " info " ] , join_with = " = " )
args . add_joined ( [ " --metafile " , metafile . path ] , join_with = " = " )
args . add_all ( ctx . attr . define , format_each = " --define: %s " )
args . add_all ( ctx . attr . external , format_each = " --external: %s " )
2021-04-07 06:53:53 +02:00
# disable the log limit and show all logs
args . add_joined ( [ " --log-limit " , " 0 " ] , join_with = " = " )
2021-03-21 06:13:01 +01:00
if ctx . attr . minify :
args . add ( " --minify " )
else :
# by default, esbuild will tree-shake 'pure' functions
# disable this unless also minifying
args . add_joined ( [ " --tree-shaking " , " ignore-annotations " ] , join_with = " = " )
if ctx . attr . sources_content :
args . add ( " --sources-content=true " )
else :
args . add ( " --sources-content=false " )
if ctx . attr . output_dir :
js_out = ctx . actions . declare_directory ( " %s " % ctx . attr . name )
outputs . append ( js_out )
args . add ( " --splitting " )
args . add_joined ( [ " --format " , " esm " ] , join_with = " = " )
args . add_joined ( [ " --outdir " , js_out . path ] , join_with = " = " )
else :
js_out = ctx . outputs . output
2021-04-07 06:53:53 +02:00
outputs . append ( js_out )
2021-03-21 06:13:01 +01:00
js_out_map = ctx . outputs . output_map
2021-04-07 06:53:53 +02:00
if ctx . attr . sourcemap != " inline " :
if js_out_map == None :
fail ( " output_map must be specified if sourcemap is not set to ' inline ' " )
outputs . append ( js_out_map )
2021-03-21 06:42:14 +01:00
if ctx . outputs . output_css :
outputs . append ( ctx . outputs . output_css )
2021-03-21 06:13:01 +01:00
if ctx . attr . format :
args . add_joined ( [ " --format " , ctx . attr . format ] , join_with = " = " )
args . add_joined ( [ " --outfile " , js_out . path ] , join_with = " = " )
jsconfig_file = write_jsconfig_file ( ctx , path_alias_mappings )
args . add_joined ( [ " --tsconfig " , jsconfig_file . path ] , join_with = " = " )
inputs . append ( jsconfig_file )
2021-03-28 00:35:27 +01:00
args . add_all ( [ ctx . expand_location ( arg ) for arg in ctx . attr . args ] )
2021-03-21 06:13:01 +01:00
2021-04-07 06:53:53 +02:00
env = { }
if ctx . attr . max_threads > 0 :
env [ " GOMAXPROCS " ] = str ( ctx . attr . max_threads )
execution_requirements = { }
if " no-remote-exec " in ctx . attr . tags :
execution_requirements = { " no-remote-exec " : " 1 " }
2021-03-21 06:13:01 +01:00
ctx . actions . run (
inputs = inputs ,
outputs = outputs ,
2021-04-07 07:19:23 +02:00
executable = ctx . toolchains [ TOOLCHAIN ] . binary ,
2021-03-21 06:13:01 +01:00
arguments = [ args ] ,
progress_message = " %s Javascript %s [esbuild] " % ( " Bundling " if not ctx . attr . output_dir else " Splitting " , entry_point . short_path ) ,
2021-04-07 06:53:53 +02:00
execution_requirements = execution_requirements ,
mnemonic = " esbuild " ,
env = env ,
2021-03-21 06:13:01 +01:00
)
return [
DefaultInfo ( files = depset ( outputs + [ jsconfig_file ] ) ) ,
]
esbuild = rule (
attrs = {
" args " : attr . string_list (
default = [ ] ,
2021-03-28 00:35:27 +01:00
doc = """ A list of extra arguments that are included in the call to esbuild.
$ ( location . . . ) can be used to resolve the path to a Bazel target . """ ,
2021-03-21 06:13:01 +01:00
) ,
" define " : attr . string_list (
default = [ ] ,
doc = """ A list of global identifier replacements.
Example :
` ` ` python
esbuild (
name = " bundle " ,
define = [
" process.env.NODE_ENV= \\ " production \\" "
] ,
)
` ` `
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #define for more details
""" ,
) ,
" deps " : attr . label_list (
default = [ ] ,
aspects = [ module_mappings_aspect , node_modules_aspect ] ,
doc = " A list of direct dependencies that are required to build the bundle " ,
) ,
" entry_point " : attr . label (
mandatory = True ,
allow_single_file = True ,
doc = " The bundle ' s entry point (e.g. your main.js or app.js or index.js) " ,
) ,
" external " : attr . string_list (
default = [ ] ,
doc = """ A list of module names that are treated as external and not included in the resulting bundle
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #external for more details
""" ,
) ,
" format " : attr . string (
values = [ " iife " , " cjs " , " esm " , " " ] ,
mandatory = False ,
doc = """ The output format of the bundle, defaults to iife when platform is browser
and cjs when platform is node . If performing code splitting , defaults to esm .
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #format for more details
""" ,
) ,
" link_workspace_root " : attr . bool (
doc = """ Link the workspace root to the bin_dir to support absolute requires like ' my_wksp/path/to/file ' .
If source files need to be required then they can be copied to the bin_dir with copy_to_bin . """ ,
) ,
2021-04-07 06:53:53 +02:00
" max_threads " : attr . int (
mandatory = False ,
doc = """ Sets the `GOMAXPROCS` variable to limit the number of threads that esbuild can run with.
This can be useful if running many esbuild rule invocations in parallel , which has the potential to cause slowdown .
For general use , leave this attribute unset .
""" ,
) ,
2021-03-21 06:13:01 +01:00
" minify " : attr . bool (
default = False ,
doc = """ Minifies the bundle with the built in minification.
Removes whitespace , shortens identifieres and uses equivalent but shorter syntax .
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
Sets all - - minify - * flags
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #minify for more details
""" ,
) ,
" output " : attr . output (
mandatory = False ,
doc = " Name of the output file when bundling " ,
) ,
" output_dir " : attr . bool (
default = False ,
doc = """ If true, esbuild produces an output directory containing all the output files from code splitting
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #splitting for more details
""" ,
) ,
" output_map " : attr . output (
mandatory = False ,
doc = " Name of the output source map when bundling " ,
) ,
2021-03-21 06:42:14 +01:00
" output_css " : attr . output (
mandatory = False ,
doc = " Name of the output css file when bundling " ,
) ,
2021-03-21 06:13:01 +01:00
" platform " : attr . string (
default = " browser " ,
values = [ " node " , " browser " , " neutral " , " " ] ,
doc = """ The platform to bundle for.
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #platform for more details
""" ,
) ,
2021-04-07 06:53:53 +02:00
" sourcemap " : attr . string (
values = [ " external " , " inline " , " both " , " " ] ,
mandatory = False ,
doc = """ Defines where sourcemaps are output and how they are included in the bundle. By default, a separate `.js.map` file is generated and referenced by the bundle. If ' external ' , a separate `.js.map` file is generated but not referenced by the bundle. If ' inline ' , a sourcemap is generated and its contents are inlined into the bundle (and no external sourcemap file is created). If ' both ' , a sourcemap is inlined and a `.js.map` file is created.
See https : / / esbuild . github . io / api / #sourcemap for more details
""" ,
) ,
2021-03-21 06:13:01 +01:00
" sources_content " : attr . bool (
mandatory = False ,
default = False ,
doc = """ If False, omits the `sourcesContent` field from generated source maps
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #sources-content for more details
""" ,
) ,
" srcs " : attr . label_list (
allow_files = True ,
default = [ ] ,
doc = """ Non-entry point JavaScript source files from the workspace.
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
You must not repeat file ( s ) passed to entry_point """ ,
) ,
" target " : attr . string (
default = " es2015 " ,
doc = """ Environment target (e.g. es2017, chrome58, firefox57, safari11,
edge16 , node10 , default esnext )
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
See https : / / esbuild . github . io / api / #target for more details
""" ,
) ,
} ,
implementation = _esbuild_impl ,
doc = """ Runs the esbuild bundler under Bazel
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
For further information about esbuild , see https : / / esbuild . github . io /
""" ,
2021-04-07 07:19:23 +02:00
toolchains = [
TOOLCHAIN ,
] ,
2021-03-21 06:13:01 +01:00
)
2021-03-21 06:42:14 +01:00
def esbuild_macro ( name , output_dir = False , output_css = False , * * kwargs ) :
2021-03-21 06:13:01 +01:00
""" esbuild helper macro around the `esbuild_bundle` rule
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
For a full list of attributes , see the ` esbuild_bundle ` rule
2021-04-07 06:53:53 +02:00
2021-03-21 06:13:01 +01:00
Args :
name : The name used for this rule and output files
output_dir : If ` True ` , produce a code split bundle in an output directory
2021-04-07 06:53:53 +02:00
output_css : If ` True ` , declare name . css as an output , which is the
2021-03-21 06:42:14 +01:00
case when your code imports a css file .
2021-03-21 06:13:01 +01:00
* * kwargs : All other args from ` esbuild_bundle `
"""
if output_dir == True :
esbuild (
name = name ,
output_dir = True ,
* * kwargs
)
else :
2021-04-07 06:53:53 +02:00
output = " %s .js " % name
if " output " in kwargs :
output = kwargs . pop ( " output " )
output_map = None
sourcemap = kwargs . get ( " sourcemap " , None )
if sourcemap != " inline " :
output_map = " %s .map " % output
2021-03-21 06:13:01 +01:00
esbuild (
name = name ,
2021-04-07 06:53:53 +02:00
output = output ,
output_map = output_map ,
2021-03-21 06:42:14 +01:00
output_css = None if not output_css else " %s .css " % name ,
2021-03-21 06:13:01 +01:00
* * kwargs
)