diff --git a/bin/j2w.mjs b/bin/j2w.mjs index ce4cb43f..9b5e8774 100755 --- a/bin/j2w.mjs +++ b/bin/j2w.mjs @@ -7,6 +7,7 @@ import { resolve, basename } from 'node:path'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import path from 'path'; +import { precompile } from "./precompile.mjs"; const componentizeVersion = '0.16.0'; const __filename = new URL(import.meta.url).pathname; @@ -103,6 +104,7 @@ async function saveBuildData(buildDataPath, checksum, version) { } const source = await readFile(src, 'utf8'); + const precompiledSource = precompile(source, src, true); // Check if a non-default wit directory is supplied const witPath = args.witPath ? resolve(args.witPath) : path.join(__dirname, 'wit'); @@ -110,7 +112,7 @@ async function saveBuildData(buildDataPath, checksum, version) { console.log(`Using user-provided wit in: ${witPath}`); } - const { component } = await componentize(source, { + const { component } = await componentize(precompiledSource, { sourceName: basename(src), witPath, worldName: args.triggerType, diff --git a/bin/precompile.mjs b/bin/precompile.mjs new file mode 100644 index 00000000..9e13d21a --- /dev/null +++ b/bin/precompile.mjs @@ -0,0 +1,62 @@ +// Copyright 2024 Fastly Inc. +// License: the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import regexpuc from 'regexpu-core'; +import { parse } from 'acorn'; +import MagicString from 'magic-string'; +import { simple as simpleWalk } from 'acorn-walk'; + +const PREAMBLE = `;{ + // Precompiled regular expressions + const precompile = (r) => { r.exec('a'); r.exec('\\u1000'); };`; +const POSTAMBLE = '}'; + +/// Emit a block of javascript that will pre-compile the regular expressions given. As spidermonkey +/// will intern regular expressions, duplicating them at the top level and testing them with both +/// an ascii and utf8 string should ensure that they won't be re-compiled when run in the fetch +/// handler. +export function precompile(source, filename = '', moduleMode = false) { + const magicString = new MagicString(source, { + filename, + }); + + const ast = parse(source, { + ecmaVersion: 'latest', + sourceType: moduleMode ? 'module' : 'script', + }); + + const precompileCalls = []; + simpleWalk(ast, { + Literal(node) { + if (!node.regex) return; + let transpiledPattern; + try { + transpiledPattern = regexpuc(node.regex.pattern, node.regex.flags, { + unicodePropertyEscapes: 'transform', + }); + } catch { + // swallow regex parse errors here to instead throw them at the engine level + // this then also avoids regex parser bugs being thrown unnecessarily + transpiledPattern = node.regex.pattern; + } + const transpiledRegex = `/${transpiledPattern}/${node.regex.flags}`; + precompileCalls.push(`precompile(${transpiledRegex});`); + magicString.overwrite(node.start, node.end, transpiledRegex); + }, + }); + + if (!precompileCalls.length) return source; + + magicString.prepend(`${PREAMBLE}${precompileCalls.join('\n')}${POSTAMBLE}`); + + // When we're ready to pipe in source maps: + // const map = magicString.generateMap({ + // source: 'source.js', + // file: 'converted.js.map', + // includeContent: true + // }); + + return magicString.toString(); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 580dab11..bcc28e61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,21 @@ { "name": "@fermyon/spin-sdk", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fermyon/spin-sdk", - "version": "3.0.0", + "version": "3.1.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bytecodealliance/componentize-js": "^0.16.0", "@fermyon/knitwit": "^0.3.0", + "acorn-walk": "^8.3.4", + "acron": "^1.0.5", + "magic-string": "^0.30.17", + "regexpu-core": "^6.2.0", "typedoc-plugin-missing-exports": "^3.0.0", "yargs": "^17.7.2" }, @@ -759,6 +763,30 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acron": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/acron/-/acron-1.0.5.tgz", + "integrity": "sha512-Jm3uey9PSUl5k9RsjBSfDJG35nG0vd9B4FUCV1J/NeVHDQ5HJPVpAhmGrPYtPKh0gLhEUIEy4DKqBy/p60B+4Q==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.11" + }, + "bin": { + "acron": "cli.js" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -1453,6 +1481,18 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1462,6 +1502,12 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", @@ -1496,6 +1542,15 @@ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", "license": "MIT" }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -1874,6 +1929,24 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regex": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", @@ -1899,6 +1972,41 @@ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", "license": "MIT" }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2272,6 +2380,46 @@ "through": "^2.3.8" } }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", diff --git a/package.json b/package.json index e8e4591f..739d85cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fermyon/spin-sdk", - "version": "3.0.0", + "version": "3.1.0", "description": "", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -28,7 +28,11 @@ "@bytecodealliance/componentize-js": "^0.16.0", "yargs": "^17.7.2", "typedoc-plugin-missing-exports": "^3.0.0", - "@fermyon/knitwit": "^0.3.0" + "@fermyon/knitwit": "^0.3.0", + "acorn-walk": "^8.3.4", + "acron": "^1.0.5", + "magic-string": "^0.30.17", + "regexpu-core": "^6.2.0" }, "files": [ "lib",