This server loads a WebAssembly Swift app, spawns multiple instances, and serves the generated HTML, enabling search engines to index the site as standard HTML without modifying the WebAssembly app.
This package is part of a Swift Stream VSCode extension.
It is designed to be used either as a standalone server or as an imported module within a project.
To run as a standalone server, configure it with environment variables:
CS_PATH_TO_WASM: Path to the WebAssembly application file.CS_SERVER_PORT: Port for the server to listen on.CS_CHILD_PROCESSES: Number of concurrent WebAssembly instances to spawn (default: 1).CS_DEBUG: Set toTRUEto enable debug logs.CS_GLOBAL_BIND: Set toTRUEto bind to 0.0.0.0.
And then run from its folder as npm run start or node main.js.
Make sure to install crawl-server globally using:
npm install -g crawl-serverThen, you can run it as follows:
- Basic usage:
crawlserver /path/to/app.wasm - Using environment variables:
crawlserver - With all options specified:
crawlserver /path/to/app.wasm -p 3322 -c 1 -d -g
Arguments:
path Path to the WebAssembly application file
Options:
-V, --version Output the version number
-p, --port Port for the server to listen on
-c, --child-processes Number of concurrent WebAssembly instances to spawn (default: 1)
-d, --debug Enable debug logs
-g, --global Bind to 0.0.0.0
-h, --help Display help for commandTo use as an imported module, install it and call the start() method:
import { start } from 'crawl-server'
start(
'/path/to/app.wasm', // path to the WebAssembly application file
{ // options:
port: 3000, // port
debug: true, // debug logs
bindGlobally: true, // bind to 0.0.0.0
numberOfInstances: 1, // number of concurrent WebAssembly instances to spawn
stateHandler: (e) => { // listen for state changes
console.log(e.state) // operating, stopping, failing
console.log(e.description) // human readable description of the situation
console.log(e.situation) // situations: server_started
// stopped_child_process
// wasm_missing
// disasterly_crashed
// respawned_after_disaster
// html_rendered
// html_not_rendered
// request_failed
// fulfilled_stop_call
}
}
)- Request Handling: Each incoming request is routed through Fastify, which first checks if thereβs a cached response.
- Caching: If a response is cached and still valid, the server responds directly with the cached HTML and
ETag. - Rendering: If the response is not cached or has expired, the server uses a child process to execute the WebAssembly app, render the HTML, and cache the result.
- Headers: Each response includes
ETagandLast-Modifiedheaders for effective caching by clients.
- Cold Start: Initializing a new WebAssembly process takes approximately 300ms.
- Warm Call: Subsequent calls to an active WebAssembly instance take about 100ms.
- Cached Response: Serving directly from the cache takes roughly 1ms.
This setup ensures fast, SEO-optimized responses for search engine crawlers, balancing performance and resource management.
When your app starts, call wasiAppOnStart() which is available in JS global scope.
Optionally, implement wasiDisableLocationChangeListener method in JS global scope, it'll be called to disable the default router in your app.
Implement wasiChangeRoute in JS global scope to handle route changes within your app.
The server will call wasiChangeRoute as follows:
global.wasiChangeRoute(
path, // route path, e.g. /articles/1
query, // query part, e.g. firstName=John&lastName=Smith
( // `rendered` handler, it should be called once page is fully rendered
expiresIn, // optional, in what time (in seconds) this content will be expired
lastModifiedAt // optional, e.g. article createdAt/editedAt unix timestamp (in seconds from 1970)
) => {
// here server renders HTML and returns it to the search engine crawler
})Contributions are welcome! Please feel free to fork the repository and submit a pull request.
This project is licensed under the MIT License.