Skip to content

Commit d1f303a

Browse files
nazar-pckripken
authored andcommitted
Fix for loading wasm files under Node.js and in browser when files we are in another dir (#5368)
With this patch WebAssembly files that are located next to JavaScript files are loaded correctly both in Node.js and in browser, even if the user loads them from a different dir than that one. We use document.scriptLocation on the web and __dirname in node.js to achieve this. When MODULARIZE is used, we also must handle the case when scriptLocation is no longer present when we call the modularize-generated function later, so we save the location beforehand. This PR also removes the `Module.*PrefixURL` options, which we numerous and made changes like this hard. Instead, `locateFile` can do all that they can. In ASSERTIONS builds we point people to add, and mention in the docs and changelog. This is also good for code size.
1 parent b2c9a15 commit d1f303a

14 files changed

+136
-86
lines changed

ChangeLog.markdown

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ Not all changes are documented here. In particular, new features, user-oriented
1010
Current Trunk
1111
-------------
1212

13+
- Fix `Module.locateFile` to resolve relative paths to *.wasm, *.mem and other files relatively to JavaScript file rather than current working directory
14+
- Add second argument `prefix` to `Module.locateFile` function that contains path to JavaScript file where files are loaded from by default
15+
- Remove `Module.*PrefixURL` APIs (use `Module.locateFile` instead)
16+
1317
v1.38.8: 07/06/2018
1418
-------------------
1519
- Fix a regression in 1.38.7 with binaryen no longer bundling binaryen.js (which emscripten doesn't need, that's just for handwritten JS users, but emscripten did check for its prescence).

emcc.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -2602,7 +2602,10 @@ def modularize():
26022602
%(src)s
26032603
26042604
return %(EXPORT_NAME)s;
2605-
}%(instantiate)s;
2605+
};
2606+
%(EXPORT_NAME)s = %(EXPORT_NAME)s.bind({
2607+
_currentScript: typeof document !== 'undefined' ? document.currentScript : undefined
2608+
})%(instantiate)s;
26062609
''' % {
26072610
'EXPORT_NAME': shared.Settings.EXPORT_NAME,
26082611
'src': src,
@@ -2708,11 +2711,7 @@ def generate_html(target, options, js_target, target_basename,
27082711
script.un_src()
27092712
script.inline = ('''
27102713
var memoryInitializer = '%s';
2711-
if (typeof Module['locateFile'] === 'function') {
2712-
memoryInitializer = Module['locateFile'](memoryInitializer);
2713-
} else if (Module['memoryInitializerPrefixURL']) {
2714-
memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
2715-
}
2714+
memoryInitializer = Module['locateFile'] ? Module['locateFile'](memoryInitializer, '') : memoryInitializer;
27162715
Module['memoryInitializerRequestURL'] = memoryInitializer;
27172716
var meminitXHR = Module['memoryInitializerRequest'] = new XMLHttpRequest();
27182717
meminitXHR.open('GET', memoryInitializer, true);

site/source/docs/api_reference/module.rst

+6-12
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Module object
55
=============
66

7-
``Module`` is a global JavaScript object with attributes that Emscripten-generated code calls at various points in its execution.
7+
``Module`` is a global JavaScript object with attributes that Emscripten-generated code calls at various points in its execution.
88

99
Developers can provide an implementation of ``Module`` to control the execution of code. For example, to define how notification messages from Emscripten are displayed, developers implement the :js:attr:`Module.print` attribute.
1010

@@ -20,7 +20,7 @@ Developers can provide an implementation of ``Module`` to control the execution
2020
Creating the Module object
2121
==========================
2222

23-
Use emcc's :ref:`pre-js option<emcc-pre-js>` to add JavaScript code that defines (or extends) the ``Module`` object with the behaviour you need.
23+
Use emcc's :ref:`pre-js option<emcc-pre-js>` to add JavaScript code that defines (or extends) the ``Module`` object with the behaviour you need.
2424

2525
When generating only JavaScript (as opposed to HTML), no ``Module`` object is created by default, and the behaviour is entirely defined by the developer. For example, creating a ``Module`` object with the following code will cause all notifications from the program to be calls to ``alert()``.
2626

@@ -51,15 +51,9 @@ The following ``Module`` attributes affect code execution. Set them to customize
5151

5252
The commandline arguments. The value of ``arguments`` contains the values returned if compiled code checks ``argc`` and ``argv``.
5353

54-
55-
.. js:attribute:: Module.filePackagePrefixURL
56-
57-
This is the "prefix" URL for a preloaded data file that is hosted separately from its JavaScript and HTML files (it includes the full path up to, but not including, the data file). See :ref:`packaging-files-data-file-location` for more information.
58-
59-
6054
.. js:attribute:: Module.locateFile
6155

62-
If set, this method will be called when the runtime needs to load a file, such as a ``.wasm`` WebAssembly file, ``.mem`` memory init file, or a file generated by the file packager. The function receives the URL, and should return the actual URL. This lets you host file packages or the ``.mem`` file etc. on a different location than the current directory (which is the default expectation), for example if you want to host them on a CDN. Note that ``locateFile`` is sort of a generalization of ``Module.filePackagePrefixURL``.
56+
If set, this method will be called when the runtime needs to load a file, such as a ``.wasm`` WebAssembly file, ``.mem`` memory init file, or a file generated by the file packager. The function receives the relative path to the file as configured in build process and a ``prefix`` (path to JavaScript file), and should return the actual URL. This lets you host file packages or the ``.mem`` file etc. on a different location than the directory of JavaScript file (which is the default expectation), for example if you want to host them on a CDN. NOTE: ``prefix`` might be empty string for memory initializer file is loaded in parallel with JS or in packager, so be careful with those.
6357

6458
.. js:attribute:: Module.logReadFiles
6559

@@ -87,7 +81,7 @@ The following ``Module`` attributes affect code execution. Set them to customize
8781
.. js:attribute:: Module.preInit
8882

8983
A function (or array of functions) that must be called before global initializers run, but after basic initialization of the JavaScript runtime. This is typically used for :ref:`File System operations <Filesystem-API>`.
90-
84+
9185
.. js:attribute:: Module.preinitializedWebGLContext
9286

9387
If building with -s GL_PREINITIALIZED_CONTEXT=1 set, you can set ``Module.preinitializedWebGLContext`` to a precreated instance of a WebGL context, which will be used later when initializing WebGL in C/C++ side. Precreating the GL context is useful if doing GL side loading (shader compilation, texture loading etc.) parallel to other page startup actions, and/or for detecting WebGL feature support, such as GL version or compressed texture support up front on a page before or in parallel to loading up any compiled code.
@@ -101,7 +95,7 @@ The following ``Module`` attributes affect code execution. Set them to customize
10195
.. js:attribute:: Module.print
10296

10397
Called when something is printed to standard output (stdout)
104-
98+
10599
.. js:attribute:: Module.printErr
106100

107101
Called when something is printed to standard error (stderr)
@@ -140,7 +134,7 @@ The generated program is able to detect its execution environment by checking th
140134

141135
However, sometimes it may be needed to override the detected environment: a typical use case would be module bundlers (like webpack): they are executed by nodejs but the final output is for browser.
142136

143-
In order to do that, you can dictate your preferred execution environment by setting the ``Module.ENVIRONMENT`` variable to one of those allowed values:
137+
In order to do that, you can dictate your preferred execution environment by setting the ``Module.ENVIRONMENT`` variable to one of those allowed values:
144138

145139
``WEB``
146140

site/source/docs/porting/files/packaging_files.rst

+16-16
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
Packaging Files
55
===============
66

7-
This topic shows how to package the files that will be used to populate :ref:`Emscripten's virtual file system <file-system-overview>` when the page is loaded.
7+
This topic shows how to package the files that will be used to populate :ref:`Emscripten's virtual file system <file-system-overview>` when the page is loaded.
88

99
There are two alternatives for how files are packaged: *preloading* and *embedding*. Embedding puts the specified files inside the generated JavaScript, while preloading packages the files separately. Embedding files is much less efficient than preloading and should only be used when packaging small numbers of small files. Preloading also enables the option to separately host the data.
10-
10+
1111
*Emcc* uses the *file packager* to package the files and generate the :ref:`File System API <Filesystem-API>` calls that create and load the file system at run time. While *Emcc* is the recommended tool for packaging, there are cases where it can make sense to run the *file packager* manually.
1212

1313
With ``--use-preload-plugins``, files can be automatically decoded based on
@@ -23,9 +23,9 @@ The command below shows how to package files for preloading:
2323
.. code-block:: bash
2424
2525
./emcc file.cpp -o file.html --preload-file asset_dir
26-
26+
2727
The command generates **file.html**, **file.js** and **file.data**. The **.data** file contains all the files in **asset_dir/**, and is loaded by **file.js**.
28-
28+
2929
.. note:: The :ref:`Tutorial <tutorial-files>` demonstrates preloading using the **hello_world_file.cpp** test code.
3030

3131

@@ -36,7 +36,7 @@ The command for embedding is shown below. In this case *emcc* generates **file.h
3636
./emcc file.cpp -o file.html --embed-file asset_dir
3737
3838
39-
By default, the files to be packaged should be nested in or below the compile-time command prompt directory. At runtime the same nested file structure is mapped to the virtual file system, with the root corresponding to the command prompt directory.
39+
By default, the files to be packaged should be nested in or below the compile-time command prompt directory. At runtime the same nested file structure is mapped to the virtual file system, with the root corresponding to the command prompt directory.
4040

4141
For example, consider a file structure **dir1/dir2/dir3/asset_dir/** where the project is compiled from **dir2**. When we package **asset_dir**, we specify its relative location **dir3/asset_dir/**:
4242

@@ -51,35 +51,35 @@ The ``@`` symbol can be used to map packaged files from any location in the loca
5151

5252
.. _packaging-files-file-packager:
5353

54-
Packaging using the file packager tool
54+
Packaging using the file packager tool
5555
======================================
5656

57-
You can also run the *file packager* manually using the instructions at the top of `file_packager.py <https://github.com/kripken/emscripten/blob/master/tools/file_packager.py>`_.
57+
You can also run the *file packager* manually using the instructions at the top of `file_packager.py <https://github.com/kripken/emscripten/blob/master/tools/file_packager.py>`_.
5858

5959
The file packager generates a **.data** file and **.js** file. The **.js** file contains the code to use the data file, and must be loaded *before* loading your main compiled code.
6060

6161
.. note::
6262

63-
- Using the *file packager* allows you to run file packaging separately from compiling the code.
63+
- Using the *file packager* allows you to run file packaging separately from compiling the code.
6464
- You can load multiple datafiles by running the file packager on each and loading the **.js** outputs. See `BananaBread <https://github.com/kripken/BananaBread>`_ for an example of this (`cube2/js/game-setup.js <https://github.com/kripken/BananaBread/blob/master/cube2/js/game-setup.js>`_).
6565

66-
66+
6767
.. _packaging-files-data-file-location:
6868

6969
Changing the data file location
7070
===============================
7171

72-
By default, the **.data** file containing all the preloaded files is loaded from the same URL as your **.js** file. In some cases it may be useful to have the data file in a different location from the other files — for example if your **.html** and **.js** change a lot you may want to keep the data file on a fast CDN somewhere else.
72+
By default, the **.data** file containing all the preloaded files is loaded from the same URL as your **.js** file. In some cases it may be useful to have the data file in a different location from the other files — for example if your **.html** and **.js** change a lot you may want to keep the data file on a fast CDN somewhere else.
7373

74-
This model is supported by changing the :js:attr:`Module.filePackagePrefixURL` to be the URL where the data file is stored (this is a prefix, so should include the full path before the data's file name). The attribute must be specified in a ``<script>`` element before the one that loads the data file.
74+
This model is supported by specifying :js:attr:`Module.locateFile` function to return URL where the data file is stored. The function must be specified in a ``<script>`` element before the one that loads the data file.
7575

7676

7777
.. _packaging-files-packaged-file-location:
7878

7979
Modifying file locations in the virtual file system
8080
===================================================
8181

82-
The default approach for packaging is to directly map the nested file structure at compile time — relative to the compile-time command prompt directory — to the root of the virtual file system. The ``@`` symbol can be used in a path at build time to *explicitly* specify where the resource will be located in the virtual file system at runtime.
82+
The default approach for packaging is to directly map the nested file structure at compile time — relative to the compile-time command prompt directory — to the root of the virtual file system. The ``@`` symbol can be used in a path at build time to *explicitly* specify where the resource will be located in the virtual file system at runtime.
8383

8484
.. note:: The ``@`` symbol is needed because sometimes it is useful to package files that are *not* nested below the compile-time directory, and for which there is therefore no default mapping to a location in the virtual file system.
8585

@@ -102,15 +102,15 @@ Valid Character Set
102102
===================
103103

104104
The following characters may be used in filenames: ``A-Z``, ``a-z``, ``0-9``, the space character and any of the characters ``!#$%&'()+,-.;=@[]^_`{}~``. Additionally, the following characters may be used if your host filesystem supports them: ``"*<>?|`` (Windows does not allow using these in filenames). When specifying the character ``@`` on the command line, it must be escaped to the form ``@@`` to avoid triggering the ``src@dst`` mapping notation (see above). The characters ``/``, ``\`` and ``:`` cannot be used.
105-
105+
106106
Monitoring file usage
107107
=====================
108108

109-
.. important:: Only package the files your app actually needs, in order to reduce download size and improve startup speed.
109+
.. important:: Only package the files your app actually needs, in order to reduce download size and improve startup speed.
110110

111111
There is an option to log which files are actually used at runtime. To use it, define the :js:attr:`Module.logReadFiles` object. Each file that is read will be logged to stderr.
112112

113-
An alternative approach is to look at :js:func:`FS.readFiles` in your compiled JavaScript. This is an object with keys for all the files that were read from. You may find it easier to use than logging as it records files rather than potentially multiple file accesses.
113+
An alternative approach is to look at :js:func:`FS.readFiles` in your compiled JavaScript. This is an object with keys for all the files that were read from. You may find it easier to use than logging as it records files rather than potentially multiple file accesses.
114114

115115
.. note:: You can also modify the :js:func:`FS.readFiles` object or remove it entirely. This can be useful, say, in order to see which files are read between two points in time in your app.
116116

@@ -145,4 +145,4 @@ The following formats are supported:
145145
Test code
146146
=========
147147

148-
The `test suite <https://github.com/kripken/emscripten/blob/master/tests/>`_ contains many file packaging examples, and is a good place to search for working code.
148+
The `test suite <https://github.com/kripken/emscripten/blob/master/tests/>`_ contains many file packaging examples, and is a good place to search for working code.

site/source/docs/porting/pthreads.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ The Emscripten implementation for the pthreads API should follow the POSIX stand
4646

4747
- Note that the function emscripten_num_logical_cores() will always return the value of navigator.hardwareConcurrency, i.e. the number of logical cores on the system, even when shared memory is not supported. This means that it is possible for emscripten_num_logical_cores() to return a value greater than 1, while at the same time emscripten_has_threading_support() can return false. The return value of emscripten_has_threading_support() denotes whether the browser has shared memory support available.
4848

49-
Also note that when compiling code that uses pthreads, an additional JavaScript file `pthread-main.js` is generated alongside the output .js file. That file must be deployed with the rest of the generated code files. By default, `pthread-main.js` will be loaded relative to the main HTML page URL. If it is desirable to load the file from a different location e.g. in a CDN environment, then one can define the `Module.locateFile(filename)` function in the main HTML `Module` object to return the URL of the target location of the `pthread-main.js` entry point. If this function is not defined in `Module`, then the relative location specified by `Module.pthreadMainPrefixURL + '/pthread-main.js'` will be used instead. If this is prefix URL is not specified either, then the default location relative to the main HTML file is used.
49+
Also note that when compiling code that uses pthreads, an additional JavaScript file `pthread-main.js` is generated alongside the output .js file. That file must be deployed with the rest of the generated code files. By default, `pthread-main.js` will be loaded relative to the main HTML page URL. If it is desirable to load the file from a different location e.g. in a CDN environment, then one can define the `Module.locateFile(filename)` function in the main HTML `Module` object to return the URL of the target location of the `pthread-main.js` entry point. If this function is not defined in `Module`, then the default location relative to the main HTML file is used.
5050

5151
Running code and tests
5252
======================

src/Fetch.js

+6-7
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,9 @@ var Fetch = {
111111

112112
var fetchJs = 'fetch-worker.js';
113113
// Allow HTML module to configure the location where the 'pthread-main.js' file will be loaded from,
114-
// either via Module.locateFile() function, or via Module.pthreadMainPrefixURL string. If neither
115-
// of these are passed, then the default URL 'pthread-main.js' relative to the main html file is loaded.
116-
if (typeof Module['locateFile'] === 'function') fetchJs = Module['locateFile'](fetchJs);
117-
else if (Module['pthreadMainPrefixURL']) fetchJs = Module['pthreadMainPrefixURL'] + fetchJs;
114+
// via Module.locateFile() function. If not specified, then the default URL 'pthread-main.js' relative
115+
// to the main html file is loaded.
116+
fetchJs = locateFile(fetchJs);
118117
Fetch.worker = new Worker(fetchJs);
119118
Fetch.worker.onmessage = function(e) {
120119
out('fetch-worker sent a message: ' + e.filename + ':' + e.lineno + ': ' + e.message);
@@ -557,15 +556,15 @@ function emscripten_start_fetch(fetch, successcb, errorcb, progresscb) {
557556
} else if (fetchAttrNoDownload) {
558557
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, reportError);
559558
} else if (fetchAttrPersistFile) {
560-
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, performCachedXhr);
559+
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, performCachedXhr);
561560
} else {
562-
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, performUncachedXhr);
561+
__emscripten_fetch_load_cached_data(Fetch.dbInstance, fetch, reportSuccess, performUncachedXhr);
563562
}
564563
} else if (!fetchAttrNoDownload) {
565564
if (fetchAttrPersistFile) {
566565
__emscripten_fetch_xhr(fetch, cacheResultAndReportSuccess, reportError, reportProgress);
567566
} else {
568-
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress);
567+
__emscripten_fetch_xhr(fetch, reportSuccess, reportError, reportProgress);
569568
}
570569
} else {
571570
#if FETCH_DEBUG

src/library_debugger_toolkit.js

+1-6
Original file line numberDiff line numberDiff line change
@@ -445,12 +445,7 @@ var CyberDWARFHeapPrinter = function(cdFileLocation) {
445445
}
446446

447447
function initialize_debugger(cb) {
448-
var cdFile;
449-
if (typeof Module['locateFile'] === 'function') {
450-
cdFileLocation = Module['locateFile'](cdFileLocation);
451-
} else if (Module['cdInitializerPrefixURL']) {
452-
cdFileLocation = Module['cdInitializerPrefixURL'] + cdFileLocation;
453-
}
448+
cdFileLocation = locateFile(cdFileLocation);
454449
if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) {
455450
var data = Module['read'](cdFileLocation);
456451
install_cyberdwarf(data);

0 commit comments

Comments
 (0)