1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Apply considerable acrobatics to get the JS/WASM deliverables building to and loadable from a directory other than the one which contains the app-level code. Requires an only-slightly-leaky abstraction of passing a URL argument when loading sqlite3.js but provides much greater flexibility in where the JS/WASM files are located.

FossilOrigin-Name: 6d468dab9eb84d4548f68014959f02fe4f66455472ff24fe729382bb2972e3d1
This commit is contained in:
stephan
2022-10-19 04:44:58 +00:00
parent 71de8e0241
commit cd0df83c15
27 changed files with 250 additions and 76 deletions

View File

@ -90,7 +90,7 @@ dir.tool := $(dir.top)/tool
# the loading-from-worker case...
#
# dir.dout = output dir for deliverables.
dir.dout := $(dir.wasm)
dir.dout := $(dir.wasm)/jswasm
# dir.tmp = output dir for intermediary build files, as opposed to
# end-user deliverables.
dir.tmp := $(dir.wasm)/bld
@ -218,6 +218,20 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-opfs.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js
# "External" API files which are part of our distribution
# but not part of the sqlite3-api.js amalgamation.
SOAP.js := $(dir.api)/sqlite3-opfs-async-proxy.js
sqlite3-worker1.js := $(dir.api)/sqlite3-worker1.js
sqlite3-worker1-promiser.js := $(dir.api)/sqlite3-worker1-promiser.js
define CP_XAPI
sqlite3-api.ext.jses += $$(dir.dout)/$$(notdir $(1))
$$(dir.dout)/$$(notdir $(1)): $(1) $$(MAKEFILE)
cp $$< $$@
endef
$(foreach X,$(SOAP.js) $(sqlite3-worker1.js) $(sqlite3-worker1-promiser.js),\
$(eval $(call CP_XAPI,$(X))))
all: $(sqlite3-api.ext.jses)
sqlite3-api.js := $(dir.tmp)/sqlite3-api.js
$(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE)
@echo "Making $@..."
@ -384,6 +398,7 @@ sqlite3-wasm.c := $(dir.api)/sqlite3-wasm.c
# difference. Thus we build all binaries against sqlite3-wasm.c
# instead of building a shared copy of sqlite3-wasm.o.
$(eval $(call call-make-pre-js,sqlite3))
$(sqlite3.js):
$(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \
$(EXPORTED_FUNCTIONS.api) \
$(pre-post-sqlite3.deps)

View File

@ -16,6 +16,27 @@
if(!originalInit){
throw new Error("Expecting self.sqlite3InitModule to be defined by the Emscripten build.");
}
/**
We need to add some state which our custom Module.locateFile()
can see, but an Emscripten limitation currently prevents us from
attaching it to the sqlite3InitModule function object:
https://github.com/emscripten-core/emscripten/issues/18071
The only current workaround is to temporarily stash this state
into the global scope and delete it when sqlite3InitModule()
is called.
*/
const initModuleState = self.sqlite3InitModuleState = Object.assign(Object.create(null),{
moduleScript: self?.document?.currentScript,
isWorker: (!self.document && self.window !== self),
location: self.location,
urlParams: new URL(self.location.href).searchParams
});
if(initModuleState.urlParams.has('sqlite3.dir')){
initModuleState.sqlite3Dir = initModuleState.urlParams.get('sqlite3.dir') +'/';
};
self.sqlite3InitModule = (...args)=>{
//console.warn("Using replaced sqlite3InitModule()",self.location);
return originalInit(...args).then((EmscriptenModule)=>{
@ -32,6 +53,8 @@
Emscripten details. */
return EmscriptenModule;
}
EmscriptenModule.sqlite3.scriptInfo = initModuleState;
//console.warn("sqlite3.scriptInfo =",EmscriptenModule.sqlite3.scriptInfo);
const f = EmscriptenModule.sqlite3.asyncPostInit;
delete EmscriptenModule.sqlite3.asyncPostInit;
return f();
@ -41,13 +64,19 @@
});
};
self.sqlite3InitModule.ready = originalInit.ready;
//console.warn("Replaced sqlite3InitModule()");
})();
if(0){
console.warn("self.location.href =",self.location.href);
if('undefined' !== typeof document){
console.warn("document.currentScript.src =",
document?.currentScript?.src);
if(self.sqlite3InitModuleState.moduleScript){
const sim = self.sqlite3InitModuleState;
let src = sim.moduleScript.src.split('/');
src.pop();
sim.scriptDir = src.join('/') + '/';
}
}
if(0){
console.warn("Replaced sqlite3InitModule()");
console.warn("self.location.href =",self.location.href);
if('undefined' !== typeof document){
console.warn("document.currentScript.src =",
document?.currentScript?.src);
}
}
})();

View File

@ -4,9 +4,49 @@
This file is intended to be prepended to the sqlite3.js build using
Emscripten's --pre-js=THIS_FILE flag (or equivalent).
*/
// See notes in extern-post-js.js
const sqlite3InitModuleState = self.sqlite3InitModuleState || Object.create(null);
delete self.sqlite3InitModuleState;
/**
This custom locateFile() tries to figure out where to load `path`
from. The intent is to provide a way for foo/bar/X.js loaded from a
Worker constructor or importScripts() to be able to resolve
foo/bar/X.wasm (in the latter case, with some help):
1) If URL param named the same as `path` is set, it is returned.
2) If sqlite3InitModuleState.sqlite3Dir is set, then (thatName + path)
is returned (note that it's assumed to end with '/').
3) If this code is running in the main UI thread AND it was loaded
from a SCRIPT tag, the directory part of that URL is used
as the prefix. (This form of resolution unfortunately does not
function for scripts loaded via importScripts().)
4) If none of the above apply, (prefix+path) is returned.
*/
Module['locateFile'] = function(path, prefix) {
return prefix + path;
};
let theFile;
const up = this.urlParams;
if(0){
console.warn("locateFile(",arguments[0], ',', arguments[1],")",
'self.location =',self.location,
'sqlite3InitModuleState.scriptDir =',this.scriptDir,
'up.entries() =',Array.from(up.entries()));
}
if(up.has(path)){
theFile = up.get(path);
}else if(this.sqlite3Dir){
theFile = this.sqlite3Dir + path;
}else if(this.scriptDir){
theFile = this.scriptDir + path;
}else{
theFile = prefix + path;
}
return theFile;
}.bind(sqlite3InitModuleState);
/**
Bug warning: this xInstantiateWasm bit must remain disabled

View File

@ -98,6 +98,9 @@ const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
options.proxyUri = callee.defaultProxyUri;
}
if('function' === typeof options.proxyUri){
options.proxyUri = options.proxyUri();
}
const thePromise = new Promise(function(promiseResolve, promiseReject_){
const loggers = {
0:console.error.bind(console),
@ -1092,9 +1095,18 @@ installOpfsVfs.defaultProxyUri =
"sqlite3-opfs-async-proxy.js";
//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
return;
}
try{
let proxyJs = installOpfsVfs.defaultProxyUri;
if(sqlite3.scriptInfo.sqlite3Dir){
installOpfsVfs.defaultProxyUri =
sqlite3.scriptInfo.sqlite3Dir + proxyJs;
//console.warn("installOpfsVfs.defaultProxyUri =",installOpfsVfs.defaultProxyUri);
}
return installOpfsVfs().catch((e)=>{
console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e);
console.warn("Ignoring inability to install OPFS sqlite3_vfs:",e.message);
});
}catch(e){
console.error("installOpfsVfs() exception:",e);

View File

@ -1334,7 +1334,20 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
//while(lip.length) p = p.then(lip.shift());
//return p.then(()=>sqlite3);
return Promise.all(lip).then(()=>sqlite3);
}
},
/**
scriptInfo ideally gets injected into this object by the
infrastructure which assembles the JS/WASM module. It contains
state which must be collected before sqlite3ApiBootstrap() can
be declared. It is not necessarily available to any
sqlite3ApiBootstrap.initializers but "should" be in place (if
it's added at all) by the time that
sqlite3ApiBootstrap.initializersAsync is processed.
This state is not part of the public API, only intended for use
with the sqlite3 API bootstrapping and wasm-loading process.
*/
scriptInfo: undefined
};
try{
sqlite3ApiBootstrap.initializers.forEach((f)=>{
@ -1410,3 +1423,4 @@ self.sqlite3ApiBootstrap.defaultConfig = Object.create(null);
value which will be stored here.
*/
self.sqlite3ApiBootstrap.sqlite3 = undefined;

View File

@ -237,6 +237,23 @@ self.sqlite3Worker1Promiser = function callee(config = callee.defaultConfig){
};
}/*sqlite3Worker1Promiser()*/;
self.sqlite3Worker1Promiser.defaultConfig = {
worker: ()=>new Worker("sqlite3-worker1.js"+self.location.search),
worker: function(){
let theJs = "sqlite3-worker1.js";
if(this.currentScript){
const src = this.currentScript.src.split('/');
src.pop();
theJs = src.join('/')+'/' + theJs;
//console.warn("promiser currentScript, theJs =",this.currentScript,theJs);
}else{
//console.warn("promiser self.location =",self.location);
const urlParams = new URL(self.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
theJs = urlParams.get('sqlite3.dir') + '/' + theJs;
}
}
return new Worker(theJs + self.location.search);
}.bind({
currentScript: self?.document?.currentScript
}),
onerror: (...args)=>console.error('worker1 promiser error',...args)
};

View File

@ -29,23 +29,21 @@
This file accepts a couple of URL arguments to adjust how it loads
sqlite3.js:
- `sqlite3.dir`, if set, treats the given directory name as the
directory from which `sqlite3.js` will be loaded.
- `sqlite3.js`, if set, is used as the URI to `sqlite3.js` and it
may contain path elements, e.g. `sqlite3.js=foo/bar/my-sqlite3.js`.
- `sqlite3.dir`, if set, treats the given directory name as the
directory from which `sqlite3.js` will be loaded.
By default is loads 'sqlite3.js'.
*/
"use strict";
(()=>{
const urlParams = new URL(self.location.href).searchParams;
let theJs;
let theJs = 'sqlite3.js';
if(urlParams.has('sqlite3.js')){
theJs = urlParams.get('sqlite3.js');
}else if(urlParams.has('sqlite3.dir')){
theJs = urlParams.get('sqlite3.dir')+'/sqlite3.js';
}else{
theJs = 'sqlite3.js';
theJs = urlParams.get('sqlite3.dir') + '/' + theJs;
}
importScripts(theJs);
sqlite3InitModule().then((sqlite3)=>{

View File

@ -69,7 +69,7 @@
<label for='cb-reverse-log-order'>Reverse log order (newest first)</label>
</span>
<div id='test-output'></div>
<script src="sqlite3-wasmfs.js"></script>
<script src="jswasm/sqlite3.js"></script>
<script src="common/SqliteTestUtil.js"></script>
<script src="batch-runner.js"></script>
<style>

View File

@ -25,7 +25,11 @@
ln.append(document.createTextNode(args.join(' ')));
document.body.append(ln);
};
const w = new Worker("demo-123.js");
const w = new Worker("demo-123.js?sqlite3.dir=jswasm"
/* Note the URL argument on that name. See
the notes in demo-123.js (search for
"importScripts") for why we need
that. */);
w.onmessage = function({data}){
switch(data.type){
case 'log':

View File

@ -18,7 +18,7 @@
</head>
<body>
<h1>1-2-sqlite3 demo</h1>
<script src="sqlite3.js"></script>
<script src="jswasm/sqlite3.js"></script>
<script src="demo-123.js"></script>
</body>
</html>

View File

@ -251,7 +251,26 @@
log("Loading and initializing sqlite3 module...");
if(self.window!==self) /*worker thread*/{
importScripts("sqlite3.js");
/*
If sqlite3.js is in a directory other than this script, in order
to get sqlite3.js to resolve sqlite3.wasm properly, we have to
explicitly tell it where sqlite3.js is being loaded from. We do
that by passing the `sqlite3.dir=theDirName` URL argument to
_this_ script. That URL argument will be seen by the JS/WASM
loader and it will adjust the sqlite3.wasm path accordingly. If
sqlite3.js/.wasm are in the same directory as this script then
that's not needed.
URL arguments passed as part of the filename via importScripts()
are simply lost, and such scripts see the self.location of
_this_ script.
*/
let sqlite3Js = 'sqlite3.js';
const urlParams = new URL(self.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
}
importScripts(sqlite3Js);
}
self.sqlite3InitModule({
// We can redirect any stdout/stderr from the module

View File

@ -42,7 +42,7 @@
.toolbar > * { margin: 0.25em; }
fieldset { border-radius: 0.5em; }
</style>
<script src="sqlite3.js"></script>
<script src="jswasm/sqlite3.js"></script>
<script src="common/SqliteTestUtil.js"></script>
<script src="demo-kvvfs1.js"></script>
</body>

View File

@ -53,14 +53,14 @@ fiddle-module.js := $(dir.fiddle)/fiddle-module.js
fiddle-module.wasm := $(subst .js,.wasm,$(fiddle-module.js))
fiddle.cses := $(dir.top)/shell.c $(sqlite3-wasm.c)
SOAP.js := sqlite3-opfs-async-proxy.js
$(dir.fiddle)/$(SOAP.js): $(SOAP.js)
fiddle.SOAP.js := $(dir.fiddle)/$(notdir $(SOAP.js))
$(fiddle.SOAP.js): $(SOAP.js)
cp $< $@
$(eval $(call call-make-pre-js,fiddle-module))
$(fiddle-module.js): $(MAKEFILE) $(MAKEFILE.fiddle) \
$(EXPORTED_FUNCTIONS.fiddle) \
$(fiddle.cses) $(pre-post-fiddle-module.deps) $(dir.fiddle)/$(SOAP.js)
$(fiddle.cses) $(pre-post-fiddle-module.deps) $(fiddle.SOAP.js)
$(emcc.bin) -o $@ $(fiddle.emcc-flags) \
$(pre-post-common.flags) $(pre-post-fiddle-module.flags) \
$(fiddle.cses)

View File

@ -169,7 +169,7 @@
return str+a.join('&nbsp;');
};
const W = new Worker("speedtest1-worker.js"+self.location.search);
const W = new Worker("speedtest1-worker.js?sqlite3.dir=jswasm");
const mPost = function(msgType,payload){
W.postMessage({type: msgType, data: payload});
};

View File

@ -1,6 +1,11 @@
'use strict';
(function(){
importScripts('common/whwasmutil.js','speedtest1.js');
let speedtestJs = 'speedtest1.js';
const urlParams = new URL(self.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
speedtestJs = urlParams.get('sqlite3.dir') + '/' + speedtestJs;
}
importScripts('common/whwasmutil.js', speedtestJs);
/**
If this environment contains OPFS, this function initializes it and
returns the name of the dir on which OPFS is mounted, else it returns

View File

@ -37,7 +37,7 @@
speedtest is running. Output will appear below when ready...
<div id='test-output'></div>
<script src="common/SqliteTestUtil.js"></script>
<script src="speedtest1.js"></script>
<script src="jswasm/speedtest1.js"></script>
<script>(function(){
/**
If this environment contains OPFS, this function initializes it and

View File

@ -9,16 +9,18 @@
<title>Async-behind-Sync experiment</title>
</head>
<body>
<header id='titlebar'><span>Async-behind-Sync Experiment</span></header>
<div>This is an experiment in wrapping the
asynchronous OPFS APIs behind a fully synchronous proxy. It is
very much incomplete, under construction, and experimental.
<header id='titlebar'><span>Async-behind-Sync sqlite3_vfs</span></header>
<div>This performs a sanity test of the "opfs" sqlite3_vfs.
<strong>See the dev console for all output.</strong>
</div>
<div>
<a href='?delete'>Use this link</a> to delete the persistent OPFS-side db (if any).
</div>
<div id='test-output'></div>
<script>new Worker("test-opfs-vfs.js"+self.location.search);</script>
<script>
new Worker(
"test-opfs-vfs.js?sqlite3.dir=jswasm&"+self.location.search.substr(1)
);
</script>
</body>
</html>

View File

@ -76,7 +76,7 @@ const tryOpfsVfs = async function(sqlite3){
log("Done!");
}/*tryOpfsVfs()*/;
importScripts('sqlite3.js');
importScripts('jswasm/sqlite3.js');
self.sqlite3InitModule()
.then((sqlite3)=>tryOpfsVfs(sqlite3))
.catch((e)=>{

View File

@ -24,7 +24,7 @@
ln.append(document.createTextNode(args.join(' ')));
logTarget.append(ln);
};
const w = new Worker("tester1.js");
const w = new Worker("tester1.js?sqlite3.dir=jswasm");
w.onmessage = function({data}){
switch(data.type){
case 'log':

View File

@ -16,7 +16,7 @@
<body>
<h1>sqlite3 WASM/JS tester #1 (UI thread)</h1>
<div id='test-output'></div>
<script src="sqlite3.js"></script>
<script src="jswasm/sqlite3.js"></script>
<script src="tester1.js"></script>
</body>
</html>

View File

@ -65,7 +65,7 @@
};
const normalizeArgs = (args)=>args.map(mapToString);
if( isUIThread() ){
console.log("Running in UI thread.");
console.log("Running in the UI thread.");
const logTarget = document.querySelector('#test-output');
logClass = function(cssClass,...args){
const ln = document.createElement('div');
@ -74,7 +74,7 @@
logTarget.append(ln);
};
}else{ /* Worker thread */
console.log("Running Worker thread.");
console.log("Running in a Worker thread.");
logClass = function(cssClass,...args){
postMessage({
type:'log',
@ -1428,7 +1428,26 @@
////////////////////////////////////////////////////////////////////////
log("Loading and initializing sqlite3 WASM module...");
if(!isUIThread()){
importScripts("sqlite3.js");
/*
If sqlite3.js is in a directory other than this script, in order
to get sqlite3.js to resolve sqlite3.wasm properly, we have to
explicitly tell it where sqlite3.js is being loaded from. We do
that by passing the `sqlite3.dir=theDirName` URL argument to
_this_ script. That URL argument will be seen by the JS/WASM
loader and it will adjust the sqlite3.wasm path accordingly. If
sqlite3.js/.wasm are in the same directory as this script then
that's not needed.
URL arguments passed as part of the filename via importScripts()
are simply lost, and such scripts see the self.location of
_this_ script.
*/
let sqlite3Js = 'sqlite3.js';
const urlParams = new URL(self.location.href).searchParams;
if(urlParams.has('sqlite3.dir')){
sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
}
importScripts(sqlite3Js);
}
self.sqlite3InitModule({
print: log,

View File

@ -28,7 +28,7 @@
<hr>
<div id='test-output'></div>
<script src="common/SqliteTestUtil.js"></script>
<script src="sqlite3-worker1-promiser.js"></script>
<script src="jswasm/sqlite3-worker1-promiser.js"></script>
<script src="testing-worker1-promiser.js"></script>
</body>
</html>

View File

@ -40,7 +40,7 @@
const promiserConfig = {
worker: ()=>{
const w = new Worker("sqlite3-worker1.js");
const w = new Worker("jswasm/sqlite3-worker1.js");
w.onerror = (event)=>error("worker.onerror",event);
return w;
},

View File

@ -19,7 +19,7 @@
'use strict';
(function(){
const T = self.SqliteTestUtil;
const SW = new Worker("sqlite3-worker1.js");
const SW = new Worker("jswasm/sqlite3-worker1.js");
const DbState = {
id: undefined
};