aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-07-01 05:12:15 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-07-01 05:12:15 -0700
commitb7d7fac635d260a09383212bfaecc3a25bfb9881 (patch)
tree6f8f76d6d1d3e735f99654024b38e19624c0cba0
parenta1d3191b89de8f6a05469cc50d7685d7d24d77d1 (diff)
downloadbun-b7d7fac635d260a09383212bfaecc3a25bfb9881.tar.gz
bun-b7d7fac635d260a09383212bfaecc3a25bfb9881.tar.zst
bun-b7d7fac635d260a09383212bfaecc3a25bfb9881.zip
this kind of works, but there is a crash when bundling. I think its missing a Stmt.Data.Store.reset()
Former-commit-id: 88aad6aeb19f3d1d73ced59a7a5aaddc2d7408ee
-rw-r--r--.vscode/launch.json7
-rw-r--r--.vscode/settings.json0
-rw-r--r--build.zig10
-rw-r--r--demos/css-stress-test/framework.tsx28
-rw-r--r--demos/css-stress-test/package.json1
-rw-r--r--demos/css-stress-test/src/index.tsx8
-rw-r--r--demos/css-stress-test/tsconfig.json2
-rw-r--r--out.txt318
-rw-r--r--src/api/schema.d.ts1
-rw-r--r--src/api/schema.js10
-rw-r--r--src/api/schema.peechy2
-rw-r--r--src/api/schema.zig10
-rw-r--r--src/cli.zig8
-rw-r--r--src/http.zig249
-rw-r--r--src/http/mime_type.zig6
-rw-r--r--src/javascript/jsc/JavascriptCore.zig23
-rw-r--r--src/javascript/jsc/base.zig234
-rw-r--r--src/javascript/jsc/javascript.zig201
-rw-r--r--src/javascript/jsc/webcore/response.zig903
-rw-r--r--src/string_immutable.zig9
-rw-r--r--src/sync.zig2
21 files changed, 1547 insertions, 485 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 2c411eaed..90fd0ceed 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,8 @@
{
"version": "0.2.0",
"configurations": [
+
+
// {
// "type": "lldb",
// "request": "launch",
@@ -65,9 +67,10 @@
"--resolve=lazy",
"--outdir=public",
"--serve",
- "--public-url=http://localhost:9000/"
+ "--public-url=http://localhost:9000/",
+ "--framework=./framework.tsx"
],
- "cwd": "${workspaceFolder}/demos/simple-react",
+ "cwd": "${workspaceFolder}/demos/css-stress-test",
"console": "internalConsole"
},
{
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/.vscode/settings.json
diff --git a/build.zig b/build.zig
index 649b77003..5b2a37e22 100644
--- a/build.zig
+++ b/build.zig
@@ -114,11 +114,12 @@ pub fn build(b: *std.build.Builder) void {
exe.setTarget(target);
exe.setBuildMode(mode);
b.install_path = output_dir;
-
+ var javascript: @TypeOf(exe) = undefined;
// exe.want_lto = true;
if (!target.getCpuArch().isWasm()) {
addPicoHTTP(exe, cwd);
- var javascript = b.addExecutable("spjs", "src/main_javascript.zig");
+ javascript = b.addExecutable("spjs", "src/main_javascript.zig");
+ addPicoHTTP(javascript, cwd);
javascript.packages = std.ArrayList(std.build.Pkg).fromOwnedSlice(std.heap.c_allocator, std.heap.c_allocator.dupe(std.build.Pkg, exe.packages.items) catch unreachable);
javascript.setOutputDir(output_dir);
javascript.setBuildMode(mode);
@@ -131,11 +132,14 @@ pub fn build(b: *std.build.Builder) void {
}
javascript.strip = false;
- javascript.install();
}
exe.install();
+ if (!target.getCpuArch().isWasm()) {
+ javascript.install();
+ }
+
const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
diff --git a/demos/css-stress-test/framework.tsx b/demos/css-stress-test/framework.tsx
new file mode 100644
index 000000000..0d1e5d18c
--- /dev/null
+++ b/demos/css-stress-test/framework.tsx
@@ -0,0 +1,28 @@
+import ReactDOMServer from "react-dom/server.browser";
+import { Base } from "./src/index";
+
+addEventListener("fetch", (event: FetchEvent) => {
+ const response = new Response(`
+ <!DOCTYPE html>
+<html>
+ <head>
+ <link
+ rel="stylesheet"
+ crossorigin="anonymous"
+ href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;700&family=Space+Mono:wght@400;700"
+ />
+ </head>
+ <body>
+ <link rel="stylesheet" href="./src/index.css" />
+ <div id="reactroot">${ReactDOMServer.renderToString(<Base />)}</div>
+
+ <script src="./src/index.tsx" async type="module"></script>
+ </body>
+</html>
+ `);
+
+ event.respondWith(response);
+});
+
+// typescript isolated modules
+export {};
diff --git a/demos/css-stress-test/package.json b/demos/css-stress-test/package.json
index 24b2082ee..fefc4d2b2 100644
--- a/demos/css-stress-test/package.json
+++ b/demos/css-stress-test/package.json
@@ -27,6 +27,7 @@
}
},
"devDependencies": {
+ "@microsoft/fetch-event-source": "^2.0.1",
"@snowpack/plugin-react-refresh": "^2.5.0",
"typescript": "^4.3.4"
}
diff --git a/demos/css-stress-test/src/index.tsx b/demos/css-stress-test/src/index.tsx
index 267691467..16855fd11 100644
--- a/demos/css-stress-test/src/index.tsx
+++ b/demos/css-stress-test/src/index.tsx
@@ -1,5 +1,3 @@
-import ReactDOMServer from "react-dom/server.browser";
-
import { Main } from "./main";
import classNames from "classnames";
const Base = ({}) => {
@@ -15,10 +13,6 @@ function startReact() {
ReactDOM.render(<Base />, document.querySelector("#reactroot"));
}
-function ssr() {
- console.log(ReactDOMServer.renderToString(<Base />));
-}
-
if (typeof window !== "undefined") {
console.log("HERE!!");
globalThis.addEventListener("DOMContentLoaded", () => {
@@ -26,8 +20,6 @@ if (typeof window !== "undefined") {
});
startReact();
-} else {
- ssr();
}
export { Base };
diff --git a/demos/css-stress-test/tsconfig.json b/demos/css-stress-test/tsconfig.json
index 401ede344..c13e650d0 100644
--- a/demos/css-stress-test/tsconfig.json
+++ b/demos/css-stress-test/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "esnext",
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": ["dom", "dom.iterable", "esnext", "WebWorker"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
diff --git a/out.txt b/out.txt
index 5a6bd56af..e69de29bb 100644
--- a/out.txt
+++ b/out.txt
@@ -1,318 +0,0 @@
-/Users/jarredsumner/Code/esdev: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/.: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./pages/..: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/.: readFile error -- IsDir/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/.: readFile error -- IsDir
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/.": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./pages/..": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/.": IsDir
-
-
-
-error: Cannot read file "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/.": IsDir
-
-
-
-error: Cannot assign to property on import "newObj"
-"use strict";exports.__esModule=true;exports.defaultHead=defaultHead;exports.default=void 0;var _react=_interopRequireWildcard(require("react"));var _sideEffect=_interopRequireDefault(require("./side-effect"));var _ampContext=require("./amp-context");var _headManagerContext=require("./head-manager-context");var _amp=require("./amp");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _getRequireWildcardCache(){if(typeof WeakMap!=="function")return null;var cache=new WeakMap();_getRequireWildcardCache=function(){return cache;};return cache;}function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache();if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}function defaultHead(inAmpMode=false){const head=[/*#__PURE__*/_react.default.createElement("meta",{charSet:"utf-8"})];if(!inAmpMode){head.push(/*#__PURE__*/_react.default.createElement("meta",{name:"viewport",content:"width=device-width"}));}return head;}function onlyReactElement(list,child){// React children can be "string" or "number" in this case we ignore them for backwards compat
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:1:1149 1148
-
-
-error: Cannot assign to property on import "metaCategories"
-switch(h.type){case'title':case'base':if(tags.has(h.type)){isUnique=false;}else{tags.add(h.type);}break;case'meta':for(let i=0,len=METATYPES.length;i<len;i++){const metatype=METATYPES[i];if(!h.props.hasOwnProperty(metatype))continue;if(metatype==='charSet'){if(metaTypes.has(metatype)){isUnique=false;}else{metaTypes.add(metatype);}}else{const category=h.props[metatype];const categories=metaCategories[metatype]||new Set();if((metatype!=='name'||!hasKey)&&categories.has(category)){isUnique=false;}else{categories.add(category);metaCategories[metatype]=categories;}}}break;}return isUnique;};}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:8:530 3158
-
-
-error: Cannot assign to property on import "newProps"
-['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:12:116 3986
-
-
-error: Cannot assign to property on import "newProps"
-['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/head.js:12:155 4025
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/@babel/runtime/helpers": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./node_modules/react": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/node_modules/react": FileNotFound
-
-
-
-error: Expected ")" but found ,
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:89 2171
-
-
-error: Expected ";" but found )
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:120 2202
-
-
-error: Expected ")" but found ,
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:338 2420
-
-
-error: Expected ";" but found )
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:340 2422
-
-
-error: Cannot assign to property on import "d"
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:157 2239
-
-
-error: Cannot assign to property on import "f"
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:259 2341
-
-
-error: Cannot assign to property on import "d"
-function J(a,b,c){var e,d={},k=null,h=null;if(null!=b)for(e in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)H.call(b,e)&&!I.hasOwnProperty(e)&&(d[e]=b[e]);var g=arguments.length-2;if(1===g)d.children=c;else if(1<g){for(var f=Array(g),m=0;m<g;m++)f[m]=arguments[m+2];d.children=f}if(a&&a.defaultProps)for(e in g=a.defaultProps,g)void 0===d[e]&&(d[e]=g[e]);return{$$typeof:n,type:a,key:k,ref:h,props:d,_owner:G.current}}
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:14:357 2439
-
-
-error: Cannot assign to property on import "e"
-exports.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(z(267,a));var e=l({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=G.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var g=a.type.defaultProps;for(f in b)H.call(b,f)&&!I.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==g?g[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){g=Array(f);for(var m=0;m<f;m++)g[m]=arguments[m+2];e.children=g}return{$$typeof:n,type:a.type,
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:20:314 4973
-
-
-error: Cannot assign to property on import "g"
-exports.cloneElement=function(a,b,c){if(null===a||void 0===a)throw Error(z(267,a));var e=l({},a.props),d=a.key,k=a.ref,h=a._owner;if(null!=b){void 0!==b.ref&&(k=b.ref,h=G.current);void 0!==b.key&&(d=""+b.key);if(a.type&&a.type.defaultProps)var g=a.type.defaultProps;for(f in b)H.call(b,f)&&!I.hasOwnProperty(f)&&(e[f]=void 0===b[f]&&void 0!==g?g[f]:b[f])}var f=arguments.length-2;if(1===f)e.children=c;else if(1<f){g=Array(f);for(var m=0;m<f;m++)g[m]=arguments[m+2];e.children=g}return{$$typeof:n,type:a.type,
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.production.min.js:20:447 5106
-
-
-error: Cannot assign to property on import "args"
- args[_key - 1] = arguments[_key];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:184:7 5256
-
-
-error: Cannot assign to property on import "args"
- args[_key2 - 1] = arguments[_key2];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:193:7 5499
-
-
-error: Cannot assign to property on import "didWarnStateUpdateForUnmountedComponent"
- didWarnStateUpdateForUnmountedComponent[warningKey] = true;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:238:5 7144
-
-
-error: Cannot assign to property on import "didWarnAboutStringRefs"
- didWarnAboutStringRefs[componentName] = true;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:622:9 19312
-
-
-error: Cannot assign to property on import "props"
- props[propName] = config[propName];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:734:9 22825
-
-
-error: Cannot assign to property on import "childArray"
- childArray[i] = arguments[i + 2];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:749:7 23234
-
-
-error: Cannot assign to property on import "props"
- props[propName] = defaultProps[propName];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:767:9 23588
-
-
-error: Cannot assign to property on import "props"
- props[propName] = defaultProps[propName];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:842:11 25862
-
-
-error: Cannot assign to property on import "props"
- props[propName] = config[propName];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:844:11 25931
-
-
-error: Cannot assign to property on import "childArray"
- childArray[i] = arguments[i + 2];
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:860:7 26350
-
-
-error: Cannot use "break" here
- break;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:1790:11 55454
-
-
-error: Cannot assign to property on import "loggedTypeFailures"
- loggedTypeFailures[error$1.message] = true;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:1932:11 60269
-
-
-error: Cannot assign to property on import "ownerHasKeyUseWarning"
- ownerHasKeyUseWarning[currentComponentErrorInfo] = true; // Usually the current owner is the offender, but if it accepts children as a
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:2038:3 62986
-
-
-error: Cannot use "break" here
- break;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./cjs/react.development.js:2169:9 67164
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/./node_modules/object-assign": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/react/node_modules/object-assign": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/object-assign": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/src/api/demo/node_modules/object-assign": FileNotFound
-
-
-
-error: Cannot read directory "/Users/jarredsumner/Code/esdev/node_modules/object-assign": FileNotFound
-
-
-
-error: Unexpected "super"
-"use strict";exports.__esModule=true;exports.default=void 0;var _react=require("react");const isServer=typeof window==='undefined';class _default extends _react.Component{constructor(props){super(props);this._hasHeadManager=void 0;this.emitChange=()=>{if(this._hasHeadManager){this.props.headManager.updateHead(this.props.reduceComponentsToState([...this.props.headManager.mountedInstances],this.props));}};this._hasHeadManager=this.props.headManager&&this.props.headManager.mountedInstances;if(isServer&&this._hasHeadManager){this.props.headManager.mountedInstances.add(this);this.emitChange();}}componentDidMount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.add(this);}this.emitChange();}componentDidUpdate(){this.emitChange();}componentWillUnmount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.delete(this);}this.emitChange();}render(){return null;}}exports.default=_default;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/next-server/lib/./side-effect.js:1:191 190
-
-
-error: Cannot assign to property on import "newObj"
-"use strict";exports.__esModule=true;exports.defaultHead=defaultHead;exports.default=void 0;var _react=_interopRequireWildcard(require("react"));var _sideEffect=_interopRequireDefault(require("./side-effect"));var _ampContext=require("./amp-context");var _headManagerContext=require("./head-manager-context");var _amp=require("./amp");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _getRequireWildcardCache(){if(typeof WeakMap!=="function")return null;var cache=new WeakMap();_getRequireWildcardCache=function(){return cache;};return cache;}function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj;}if(obj===null||typeof obj!=="object"&&typeof obj!=="function"){return{default:obj};}var cache=_getRequireWildcardCache();if(cache&&cache.has(obj)){return cache.get(obj);}var newObj={};var hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;if(desc&&(desc.get||desc.set)){Object.defineProperty(newObj,key,desc);}else{newObj[key]=obj[key];}}}newObj.default=obj;if(cache){cache.set(obj,newObj);}return newObj;}function defaultHead(inAmpMode=false){const head=[/*#__PURE__*/_react.default.createElement("meta",{charSet:"utf-8"})];if(!inAmpMode){head.push(/*#__PURE__*/_react.default.createElement("meta",{name:"viewport",content:"width=device-width"}));}return head;}function onlyReactElement(list,child){// React children can be "string" or "number" in this case we ignore them for backwards compat
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:1:1149 1148
-
-
-error: Cannot assign to property on import "metaCategories"
-switch(h.type){case'title':case'base':if(tags.has(h.type)){isUnique=false;}else{tags.add(h.type);}break;case'meta':for(let i=0,len=METATYPES.length;i<len;i++){const metatype=METATYPES[i];if(!h.props.hasOwnProperty(metatype))continue;if(metatype==='charSet'){if(metaTypes.has(metatype)){isUnique=false;}else{metaTypes.add(metatype);}}else{const category=h.props[metatype];const categories=metaCategories[metatype]||new Set();if((metatype!=='name'||!hasKey)&&categories.has(category)){isUnique=false;}else{categories.add(category);metaCategories[metatype]=categories;}}}break;}return isUnique;};}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:8:530 3158
-
-
-error: Cannot assign to property on import "newProps"
-['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:12:116 3986
-
-
-error: Cannot assign to property on import "newProps"
-['https://fonts.googleapis.com/css'].some(url=>c.props['href'].startsWith(url))){const newProps={...(c.props||{})};newProps['data-href']=newProps['href'];newProps['href']=undefined;return/*#__PURE__*/_react.default.cloneElement(c,newProps);}}return/*#__PURE__*/_react.default.cloneElement(c,{key});});}/**
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/head.js:12:155 4025
-
-
-error: Unexpected "super"
-"use strict";exports.__esModule=true;exports.default=void 0;var _react=require("react");const isServer=typeof window==='undefined';class _default extends _react.Component{constructor(props){super(props);this._hasHeadManager=void 0;this.emitChange=()=>{if(this._hasHeadManager){this.props.headManager.updateHead(this.props.reduceComponentsToState([...this.props.headManager.mountedInstances],this.props));}};this._hasHeadManager=this.props.headManager&&this.props.headManager.mountedInstances;if(isServer&&this._hasHeadManager){this.props.headManager.mountedInstances.add(this);this.emitChange();}}componentDidMount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.add(this);}this.emitChange();}componentDidUpdate(){this.emitChange();}componentWillUnmount(){if(this._hasHeadManager){this.props.headManager.mountedInstances.delete(this);}this.emitChange();}render(){return null;}}exports.default=_default;
-
-/Users/jarredsumner/Code/esdev/src/api/demo/./node_modules/next/./dist/client/../next-server/lib/./side-effect.js:1:191 190
diff --git a/src/api/schema.d.ts b/src/api/schema.d.ts
index a78156e10..4b647c4db 100644
--- a/src/api/schema.d.ts
+++ b/src/api/schema.d.ts
@@ -256,6 +256,7 @@ type uint32 = number;
only_scan_dependencies?: ScanDependencyMode;
generate_node_module_bundle?: boolean;
node_modules_bundle_path?: string;
+ javascript_framework_file?: string;
}
export interface FileHandle {
diff --git a/src/api/schema.js b/src/api/schema.js
index e48d857a6..a8241f345 100644
--- a/src/api/schema.js
+++ b/src/api/schema.js
@@ -671,6 +671,10 @@ function decodeTransformOptions(bb) {
result["node_modules_bundle_path"] = bb.readString();
break;
+ case 22:
+ result["javascript_framework_file"] = bb.readString();
+ break;
+
default:
throw new Error("Attempted to parse invalid message");
}
@@ -835,6 +839,12 @@ bb.writeByte(encoded);
bb.writeByte(21);
bb.writeString(value);
}
+
+ var value = message["javascript_framework_file"];
+ if (value != null) {
+ bb.writeByte(22);
+ bb.writeString(value);
+ }
bb.writeByte(0);
}
diff --git a/src/api/schema.peechy b/src/api/schema.peechy
index 1da6c8c67..6f755e907 100644
--- a/src/api/schema.peechy
+++ b/src/api/schema.peechy
@@ -168,6 +168,8 @@ message TransformOptions {
bool generate_node_module_bundle = 20;
string node_modules_bundle_path = 21;
+
+ string javascript_framework_file = 22;
}
struct FileHandle {
diff --git a/src/api/schema.zig b/src/api/schema.zig
index 0805ddfc1..23739441a 100644
--- a/src/api/schema.zig
+++ b/src/api/schema.zig
@@ -823,6 +823,9 @@ generate_node_module_bundle: ?bool = null,
/// node_modules_bundle_path
node_modules_bundle_path: ?[]const u8 = null,
+/// javascript_framework_file
+javascript_framework_file: ?[]const u8 = null,
+
pub fn decode(reader: anytype) anyerror!TransformOptions {
var this = std.mem.zeroes(TransformOptions);
@@ -894,6 +897,9 @@ pub fn decode(reader: anytype) anyerror!TransformOptions {
21 => {
this.node_modules_bundle_path = try reader.readValue([]const u8);
},
+ 22 => {
+ this.javascript_framework_file = try reader.readValue([]const u8);
+},
else => {
return error.InvalidMessage;
},
@@ -987,6 +993,10 @@ if (this.node_modules_bundle_path) |node_modules_bundle_path| {
try writer.writeFieldID(21);
try writer.writeValue(node_modules_bundle_path);
}
+if (this.javascript_framework_file) |javascript_framework_file| {
+ try writer.writeFieldID(22);
+ try writer.writeValue(javascript_framework_file);
+}
try writer.endMessage();
}
diff --git a/src/cli.zig b/src/cli.zig
index b89343ec0..05aa6feae 100644
--- a/src/cli.zig
+++ b/src/cli.zig
@@ -130,6 +130,7 @@ pub const Cli = struct {
clap.parseParam("--scan Instead of bundling or transpiling, print a list of every file imported by an entry point, recursively") catch unreachable,
clap.parseParam("--new-jsb Generate a new node_modules.jsb file from node_modules and entry point(s)") catch unreachable,
clap.parseParam("--jsb <STR> Use a Speedy JavaScript Bundle (default: \"./node_modules.jsb\" if exists)") catch unreachable,
+ clap.parseParam("--framework <STR> Use a JavaScript framework (file path) with --serve") catch unreachable,
// clap.parseParam("--no-jsb Use a Speedy JavaScript Bundle (default: \"./node_modules.jsb\" if exists)") catch unreachable,
clap.parseParam("<POS>... Entry points to use") catch unreachable,
};
@@ -183,6 +184,8 @@ pub const Cli = struct {
var jsx_production = args.flag("--jsx-production");
var react_fast_refresh = false;
+ var javascript_framework = args.option("--framework");
+
if (serve or args.flag("--new-jsb")) {
react_fast_refresh = true;
if (args.flag("--disable-react-fast-refresh") or jsx_production) {
@@ -280,6 +283,10 @@ pub const Cli = struct {
std.process.exit(1);
}
+ if (!serve) {
+ javascript_framework = null;
+ }
+
return Api.TransformOptions{
.jsx = jsx,
.output_dir = output_dir,
@@ -307,6 +314,7 @@ pub const Cli = struct {
.platform = platform,
.only_scan_dependencies = if (args.flag("--scan")) Api.ScanDependencyMode.all else Api.ScanDependencyMode._none,
.generate_node_module_bundle = if (args.flag("--new-jsb")) true else false,
+ .javascript_framework_file = javascript_framework,
};
}
};
diff --git a/src/http.zig b/src/http.zig
index 78d9c624f..a0f899ddd 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -12,6 +12,7 @@ const logger = @import("logger.zig");
const Fs = @import("./fs.zig");
const Options = @import("./options.zig");
const Css = @import("css_scanner.zig");
+const NodeModuleBundle = @import("./node_module_bundle.zig").NodeModuleBundle;
pub fn constStrToU8(s: string) []u8 {
return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len];
@@ -31,8 +32,8 @@ const picohttp = @import("picohttp");
const Header = picohttp.Header;
const Request = picohttp.Request;
const Response = picohttp.Response;
-const Headers = picohttp.Headers;
-const MimeType = @import("http/mime_type.zig");
+pub const Headers = picohttp.Headers;
+pub const MimeType = @import("http/mime_type.zig");
const Bundler = bundler.ServeBundler;
const Websocket = @import("./http/websocket.zig");
const js_printer = @import("js_printer.zig");
@@ -41,6 +42,8 @@ const watcher = @import("./watcher.zig");
threadlocal var req_headers_buf: [100]picohttp.Header = undefined;
threadlocal var res_headers_buf: [100]picohttp.Header = undefined;
const sync = @import("./sync.zig");
+const JavaScript = @import("./javascript/jsc/JavaScript.zig");
+const js = @import("javascript/jsc/javascript.zig");
pub const Watcher = watcher.NewWatcher(*Server);
@@ -189,10 +192,23 @@ pub const RequestContext = struct {
watcher: *Watcher,
timer: std.time.Timer,
+ full_url: [:0]const u8 = "",
res_headers_count: usize = 0,
pub const bundle_prefix = "__speedy";
+ pub fn getFullURL(this: *RequestContext) [:0]const u8 {
+ if (this.full_url.len == 0) {
+ if (this.bundler.options.public_url.len > 0) {
+ this.full_url = std.fmt.allocPrintZ(this.allocator, "{s}{s}", .{ this.bundler.options.public_url, this.request.path }) catch unreachable;
+ } else {
+ this.full_url = this.allocator.dupeZ(u8, this.request.path) catch unreachable;
+ }
+ }
+
+ return this.full_url;
+ }
+
pub fn header(ctx: *RequestContext, comptime name: anytype) ?Header {
if (name.len < 17) {
for (ctx.request.headers) |head| {
@@ -246,6 +262,17 @@ pub const RequestContext = struct {
try ctx.flushHeaders();
}
+ pub fn clearHeaders(
+ this: *RequestContext,
+ ) !void {
+ this.res_headers_count = 0;
+ }
+
+ pub fn appendHeaderSlow(this: *RequestContext, name: string, value: string) !void {
+ res_headers_buf[this.res_headers_count] = picohttp.Header{ .name = name, .value = value };
+ this.res_headers_count += 1;
+ }
+
threadlocal var resp_header_out_buf: [4096]u8 = undefined;
pub fn flushHeaders(ctx: *RequestContext) !void {
if (ctx.res_headers_count == 0) return;
@@ -288,6 +315,20 @@ pub const RequestContext = struct {
ctx.status = code;
}
+ threadlocal var status_buf: [std.fmt.count("HTTP/1.1 {d} {s}\r\n", .{ 200, "OK" })]u8 = undefined;
+ pub fn writeStatusSlow(ctx: *RequestContext, code: u16) !void {
+ _ = try ctx.writeSocket(
+ try std.fmt.bufPrint(
+ &status_buf,
+ "HTTP/1.1 {d} {s}\r\n",
+ .{ code, if (code > 299) "HM" else "OK" },
+ ),
+ SOCKET_FLAGS,
+ );
+
+ ctx.status = @truncate(HTTPStatusCode, code);
+ }
+
pub fn init(
req: Request,
arena: std.heap.ArenaAllocator,
@@ -592,6 +633,132 @@ pub const RequestContext = struct {
}
};
+ pub const JavaScriptHandler = struct {
+ ctx: RequestContext,
+ conn: tcp.Connection,
+
+ pub const HandlerThread = struct {
+ args: Api.TransformOptions,
+ existing_bundle: ?*NodeModuleBundle,
+ log: ?*logger.Log = null,
+ };
+
+ pub const Channel = sync.Channel(*JavaScriptHandler, .{ .Static = 100 });
+ pub var channel: Channel = undefined;
+ var has_loaded_channel = false;
+ pub var javascript_disabled = false;
+ var thread: std.Thread = undefined;
+ pub fn spawnThread(handler: HandlerThread) !void {
+ _ = try std.Thread.spawn(spawn, handler);
+ }
+
+ pub fn spawn(handler: HandlerThread) void {
+ var _handler = handler;
+ _spawn(&_handler) catch {};
+ }
+
+ pub fn _spawn(handler: *HandlerThread) !void {
+ defer {
+ javascript_disabled = true;
+ }
+
+ var stdout = std.io.getStdOut();
+ // var stdout = std.io.bufferedWriter(stdout_file.writer());
+ var stderr = std.io.getStdErr();
+ // var stderr = std.io.bufferedWriter(stderr_file.writer());
+ var output_source = Output.Source.init(stdout, stderr);
+ // defer stdout.flush() catch {};
+ // defer stderr.flush() catch {};
+ Output.Source.set(&output_source);
+ Output.enable_ansi_colors = stderr.isTty();
+ js_ast.Stmt.Data.Store.create(std.heap.c_allocator);
+ js_ast.Expr.Data.Store.create(std.heap.c_allocator);
+
+ defer Output.flush();
+ var boot = handler.args.javascript_framework_file.?;
+ var vm = try JavaScript.VirtualMachine.init(std.heap.c_allocator, handler.args, handler.existing_bundle, handler.log);
+ defer vm.deinit();
+
+ var resolved_entry_point = try vm.bundler.resolver.resolve(
+ std.fs.path.dirname(boot).?,
+ boot,
+ .entry_point,
+ );
+ JavaScript.VirtualMachine.instance = vm;
+ var exception: js.JSValueRef = null;
+ var load_result = try JavaScript.Module.loadFromResolveResult(vm, vm.ctx, resolved_entry_point, &exception);
+
+ // We've already printed the exception here!
+ if (exception != null) {
+ Output.prettyErrorln(
+ "JavaScript VM failed to start",
+ .{},
+ );
+ Output.flush();
+
+ return;
+ }
+
+ switch (load_result) {
+ .Module => {
+ Output.prettyln(
+ "Loaded JavaScript VM: \"{s}\"",
+ .{vm.bundler.fs.relativeTo(boot)},
+ );
+ Output.flush();
+ },
+ .Path => {
+ Output.prettyErrorln(
+ "Error loading JavaScript VM: Expected framework to be a path to a JavaScript-like file but received \"{s}\"",
+ .{boot},
+ );
+ Output.flush();
+ return;
+ },
+ }
+
+ js_ast.Stmt.Data.Store.reset();
+ js_ast.Expr.Data.Store.reset();
+
+ try runLoop(vm);
+ }
+
+ pub fn runLoop(vm: *JavaScript.VirtualMachine) !void {
+ while (true) {
+ defer {
+ js_ast.Stmt.Data.Store.reset();
+ js_ast.Expr.Data.Store.reset();
+ }
+ var handler: *JavaScriptHandler = try channel.readItem();
+ try JavaScript.EventListenerMixin.emitFetchEvent(vm, &handler.ctx);
+ }
+ }
+
+ var one: [1]*JavaScriptHandler = undefined;
+ pub fn enqueue(ctx: *RequestContext, server: *Server) !void {
+ var clone = try ctx.allocator.create(JavaScriptHandler);
+ clone.ctx = ctx.*;
+ clone.conn = ctx.conn.*;
+ clone.ctx.conn = &clone.conn;
+
+ if (!has_loaded_channel) {
+ has_loaded_channel = true;
+ channel = Channel.init();
+ try JavaScriptHandler.spawnThread(
+ HandlerThread{
+ .args = server.transform_options,
+ .existing_bundle = server.bundler.options.node_modules_bundle,
+ .log = &server.log,
+ },
+ );
+ }
+
+ defer ctx.controlled = true;
+ one[0] = clone;
+ _ = try channel.write(&one);
+ }
+ };
+
pub const WebsocketHandler = struct {
accept_key: [28]u8 = undefined,
ctx: RequestContext,
@@ -943,6 +1110,22 @@ pub const RequestContext = struct {
}
};
+ pub fn writeETag(this: *RequestContext, buffer: anytype) !bool {
+ const strong_etag = std.hash.Wyhash.hash(1, buffer);
+ const etag_content_slice = std.fmt.bufPrintIntToSlice(strong_etag_buffer[0..49], strong_etag, 16, .upper, .{});
+
+ this.appendHeader("ETag", etag_content_slice);
+
+ if (this.header("If-None-Match")) |etag_header| {
+ if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
+ try this.sendNotModified();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
pub fn handleWebsocket(ctx: *RequestContext) anyerror!void {
ctx.controlled = true;
var handler = try WebsocketHandler.addWebsocket(ctx);
@@ -1228,18 +1411,8 @@ pub const RequestContext = struct {
}
if (FeatureFlags.strong_etags_for_built_files) {
- // TODO: don't hash runtime.js
- const strong_etag = std.hash.Wyhash.hash(1, buffer);
- const etag_content_slice = std.fmt.bufPrintIntToSlice(strong_etag_buffer[0..49], strong_etag, 16, .upper, .{});
-
- ctx.appendHeader("ETag", etag_content_slice);
-
- if (ctx.header("If-None-Match")) |etag_header| {
- if (std.mem.eql(u8, etag_content_slice, etag_header.value)) {
- try ctx.sendNotModified();
- return;
- }
- }
+ const did_send = ctx.writeETag(buffer) catch false;
+ if (did_send) return;
}
if (buffer.len == 0) {
@@ -1313,6 +1486,7 @@ pub const Server = struct {
bundler: Bundler,
watcher: *Watcher,
timer: std.time.Timer = undefined,
+ transform_options: Api.TransformOptions,
pub fn adjustUlimit() !void {
var limit = try std.os.getrlimit(.NOFILE);
@@ -1465,17 +1639,25 @@ pub const Server = struct {
req_ctx.keep_alive = false;
}
- req_ctx.handleRequest() catch |err| {
- switch (err) {
- error.ModuleNotFound => {
- req_ctx.sendNotFound() catch {};
- },
- else => {
- Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
- return;
- },
+ if (req_ctx.url.extname.len == 0 and !RequestContext.JavaScriptHandler.javascript_disabled) {
+ if (server.transform_options.javascript_framework_file != null) {
+ RequestContext.JavaScriptHandler.enqueue(&req_ctx, server) catch unreachable;
}
- };
+ }
+
+ if (!req_ctx.controlled) {
+ req_ctx.handleRequest() catch |err| {
+ switch (err) {
+ error.ModuleNotFound => {
+ req_ctx.sendNotFound() catch {};
+ },
+ else => {
+ Output.printErrorln("FAIL [{s}] - {s}: {s}", .{ @errorName(err), req.method, req.path });
+ return;
+ },
+ }
+ };
+ }
if (!req_ctx.controlled) {
const status = req_ctx.status orelse @intCast(HTTPStatusCode, 500);
@@ -1503,11 +1685,30 @@ pub const Server = struct {
.log = log,
.bundler = undefined,
.watcher = undefined,
+ .transform_options = options,
.timer = try std.time.Timer.start(),
};
server.bundler = try Bundler.init(allocator, &server.log, options, null);
server.bundler.configureLinker();
+ load_framework: {
+ if (options.javascript_framework_file) |framework_file_path| {
+ var framework_file = server.bundler.normalizeEntryPointPath(framework_file_path);
+ var resolved = server.bundler.resolver.resolve(
+ server.bundler.fs.top_level_dir,
+ framework_file,
+ .entry_point,
+ ) catch |err| {
+ Output.prettyError("Failed to load framework: {s}", .{@errorName(err)});
+ Output.flush();
+ server.transform_options.javascript_framework_file = null;
+ break :load_framework;
+ };
+
+ server.transform_options.javascript_framework_file = try server.allocator.dupe(u8, resolved.path_pair.primary.text);
+ }
+ }
+
try server.initWatcher();
try server.run();
diff --git a/src/http/mime_type.zig b/src/http/mime_type.zig
index b7139d1c0..dc8c40cc2 100644
--- a/src/http/mime_type.zig
+++ b/src/http/mime_type.zig
@@ -29,6 +29,8 @@ pub const other = MimeType.init("application/octet-stream", .other);
pub const css = MimeType.init("text/css", .css);
pub const javascript = MimeType.init("text/javascript;charset=utf-8", .javascript);
pub const ico = MimeType.init("image/vnd.microsoft.icon", .image);
+pub const html = MimeType.init("text/html;charset=utf-8", .html);
+pub const json = MimeType.init("application/json;charset=utf-8", .json);
fn init(comptime str: string, t: Category) MimeType {
return MimeType{
@@ -89,11 +91,11 @@ pub fn byExtension(ext: string) MimeType {
},
4 => {
return switch (Four.match(ext)) {
- Four.case("json") => MimeType.init("application/json;charset=utf-8", .json),
+ Four.case("json") => MimeType.json,
Four.case("jpeg") => MimeType.init("image/jpeg", .image),
Four.case("aiff") => MimeType.init("image/png", .image),
Four.case("tiff") => MimeType.init("image/tiff", .image),
- Four.case("html") => MimeType.init("text/html;charset=utf-8", .html),
+ Four.case("html") => MimeType.html,
Four.case("wasm") => MimeType.init(
"application/wasm",
.wasm,
diff --git a/src/javascript/jsc/JavascriptCore.zig b/src/javascript/jsc/JavascriptCore.zig
index 83ccaebcf..0f1a624ce 100644
--- a/src/javascript/jsc/JavascriptCore.zig
+++ b/src/javascript/jsc/JavascriptCore.zig
@@ -246,3 +246,26 @@ pub const OpaqueJSValue = struct_OpaqueJSValue;
// https://github.com/WebKit/webkit/blob/main/Source/JavaScriptCore/API/JSStringRef.cpp#L62
pub extern fn JSStringCreateWithCharactersNoCopy(string: [*c]const JSChar, numChars: size_t) JSStringRef;
const size_t = usize;
+
+const then_key = "then";
+var thenable_string: JSStringRef = null;
+pub fn isObjectOfClassAndResolveIfNeeded(ctx: JSContextRef, obj: JSObjectRef, class: JSClassRef) ?JSObjectRef {
+ if (JSValueIsObjectOfClass(ctx, obj, class)) {
+ return obj;
+ }
+
+ if (!JSValueIsObject(ctx, obj)) {
+ return null;
+ }
+
+ if (thenable_string == null) {
+ thenable_string = JSStringCreateWithUTF8CString(then_key[0.. :0]);
+ }
+
+ var prop = JSObjectGetPropertyForKey(ctx, obj, JSValueMakeString(ctx, thenable_string), null);
+ if (prop == null) {
+ return null;
+ }
+
+
+}
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index e9bc819ff..307bf2da9 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -33,6 +33,79 @@ pub const To = struct {
return function;
}
+ pub fn Finalize(
+ comptime ZigContextType: type,
+ comptime ctxfn: fn (
+ this: *ZigContextType,
+ object: js.JSObjectRef,
+ ) void,
+ ) type {
+ return struct {
+ pub fn rfn(
+ object: js.JSObjectRef,
+ ) callconv(.C) void {
+ var object_ptr_ = js.JSObjectGetPrivate(object);
+ if (object_ptr_ == null) return;
+
+ return ctxfn(
+ @ptrCast(*ZigContextType, @alignCast(@alignOf(*ZigContextType), object_ptr_.?)),
+ object,
+ );
+ }
+ };
+ }
+
+ pub fn Constructor(
+ comptime ctxfn: fn (
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef,
+ ) type {
+ return struct {
+ pub fn rfn(
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ argumentCount: usize,
+ arguments: [*c]const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ return ctxfn(
+ ctx,
+ function,
+ if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{},
+ exception,
+ );
+ }
+ };
+ }
+ pub fn ConstructorCallback(
+ comptime ctxfn: fn (
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef,
+ ) type {
+ return struct {
+ pub fn rfn(
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ argumentCount: usize,
+ arguments: [*c]const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ return ctxfn(
+ ctx,
+ function,
+ if (arguments) |args| args[0..argumentCount] else &[_]js.JSValueRef{},
+ exception,
+ );
+ }
+ };
+ }
+
pub fn Callback(
comptime ZigContextType: type,
comptime ctxfn: fn (
@@ -107,6 +180,18 @@ pub const Properties = struct {
pub const initialize_bundled_module = "$$m";
pub const load_module_function = "$lOaDuRcOdE$";
pub const window = "window";
+ pub const default = "default";
+ pub const include = "include";
+
+ pub const GET = "GET";
+ pub const PUT = "PUT";
+ pub const POST = "POST";
+ pub const PATCH = "PATCH";
+ pub const HEAD = "HEAD";
+ pub const OPTIONS = "OPTIONS";
+
+ pub const navigate = "navigate";
+ pub const follow = "follow";
};
pub const UTF16 = struct {
@@ -125,24 +210,50 @@ pub const Properties = struct {
pub const initialize_bundled_module = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.initialize_bundled_module);
pub const load_module_function: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.load_module_function);
pub const window: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.window);
+ pub const default: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.default);
+ pub const include: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.include);
+
+ pub const GET: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.GET);
+ pub const PUT: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.PUT);
+ pub const POST: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.POST);
+ pub const PATCH: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.PATCH);
+ pub const HEAD: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.HEAD);
+ pub const OPTIONS: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.OPTIONS);
+
+ pub const navigate: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.navigate);
+ pub const follow: []c_ushort = std.unicode.utf8ToUtf16LeStringLiteral(UTF8.follow);
};
pub const Refs = struct {
- pub var module: js.JSStringRef = null;
- pub var globalThis: js.JSStringRef = null;
- pub var exports: js.JSStringRef = null;
- pub var log: js.JSStringRef = null;
- pub var debug: js.JSStringRef = null;
- pub var info: js.JSStringRef = null;
- pub var error_: js.JSStringRef = null;
- pub var warn: js.JSStringRef = null;
- pub var console: js.JSStringRef = null;
- pub var require: js.JSStringRef = null;
- pub var description: js.JSStringRef = null;
- pub var name: js.JSStringRef = null;
- pub var initialize_bundled_module: js.JSStringRef = null;
- pub var load_module_function: js.JSStringRef = null;
- pub var window: js.JSStringRef = null;
+ pub var module: js.JSStringRef = undefined;
+ pub var globalThis: js.JSStringRef = undefined;
+ pub var exports: js.JSStringRef = undefined;
+ pub var log: js.JSStringRef = undefined;
+ pub var debug: js.JSStringRef = undefined;
+ pub var info: js.JSStringRef = undefined;
+ pub var error_: js.JSStringRef = undefined;
+ pub var warn: js.JSStringRef = undefined;
+ pub var console: js.JSStringRef = undefined;
+ pub var require: js.JSStringRef = undefined;
+ pub var description: js.JSStringRef = undefined;
+ pub var name: js.JSStringRef = undefined;
+ pub var initialize_bundled_module: js.JSStringRef = undefined;
+ pub var load_module_function: js.JSStringRef = undefined;
+ pub var window: js.JSStringRef = undefined;
+ pub var default: js.JSStringRef = undefined;
+ pub var include: js.JSStringRef = undefined;
+ pub var GET: js.JSStringRef = undefined;
+ pub var PUT: js.JSStringRef = undefined;
+ pub var POST: js.JSStringRef = undefined;
+ pub var PATCH: js.JSStringRef = undefined;
+ pub var HEAD: js.JSStringRef = undefined;
+ pub var OPTIONS: js.JSStringRef = undefined;
+
+ pub var empty_string_ptr = [_]u8{0};
+ pub var empty_string: js.JSStringRef = undefined;
+
+ pub var navigate: js.JSStringRef = undefined;
+ pub var follow: js.JSStringRef = undefined;
};
pub fn init() void {
@@ -160,9 +271,13 @@ pub const Properties = struct {
);
}
}
+
+ Refs.empty_string = js.JSStringCreateWithUTF8CString(&Refs.empty_string_ptr);
}
};
+const hasSetter = std.meta.trait.hasField("set");
+const hasFinalize = std.meta.trait.hasField("finalize");
pub fn NewClass(
comptime ZigType: type,
comptime name: string,
@@ -215,6 +330,65 @@ pub fn NewClass(
};
var static_properties: [property_names.len]js.JSStaticValue = undefined;
+ pub var ref: js.JSClassRef = null;
+ pub var loaded = false;
+ pub var definition: js.JSClassDefinition = undefined;
+ const ConstructorWrapper = struct {
+ pub fn rfn(
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ argumentCount: usize,
+ arguments: [*c]const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ return definition.callAsConstructor.?(ctx, function, argumentCount, arguments, exception);
+ }
+ };
+
+ pub const Constructor = ConstructorWrapper.rfn;
+
+ pub const static_value_count = static_properties.len;
+
+ pub fn get() callconv(.C) [*c]js.JSClassRef {
+ if (!loaded) {
+ loaded = true;
+ definition = define();
+ ref = js.JSClassRetain(js.JSClassCreate(&definition));
+ }
+ return &ref;
+ }
+
+ pub fn RawGetter(comptime ReceiverType: type) type {
+ const ClassGetter = struct {
+ pub fn getter(
+ ctx: js.JSContextRef,
+ obj: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSValueRef {
+ return js.JSObjectMake(ctx, get().*, null);
+ }
+ };
+
+ return ClassGetter;
+ }
+
+ pub fn GetClass(comptime ReceiverType: type) type {
+ const ClassGetter = struct {
+ pub fn getter(
+ receiver: *ReceiverType,
+ ctx: js.JSContextRef,
+ obj: js.JSObjectRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSObjectMake(ctx, get().*, null);
+ }
+ };
+
+ return ClassGetter;
+ }
+
pub fn getPropertyCallback(
ctx: js.JSContextRef,
obj: js.JSObjectRef,
@@ -281,7 +455,7 @@ pub fn NewClass(
),
);
- var exc: js.ExceptionRef = null;
+ var exc: js.JSValueRef = null;
switch (comptime @typeInfo(@TypeOf(@field(
properties,
@@ -372,15 +546,18 @@ pub fn NewClass(
var count: usize = 0;
inline for (function_name_literals) |function_name, i| {
if (comptime strings.eqlComptime(function_names[i], "constructor")) {
- def.callAsConstructor = @field(staticFunctions, function_names[i]);
+ def.callAsConstructor = To.JS.Constructor(@field(staticFunctions, function_names[i])).rfn;
+ } else if (comptime strings.eqlComptime(function_names[i], "finalize")) {
+ def.finalize = To.JS.Finalize(ZigType, staticFunctions.finalize).rfn;
} else {
- count += 1;
var callback = To.JS.Callback(ZigType, @field(staticFunctions, function_names[i])).rfn;
static_functions[count] = js.JSStaticFunction{
.name = (function_names[i][0.. :0]).ptr,
.callAsFunction = callback,
.attributes = comptime if (read_only) js.JSPropertyAttributes.kJSPropertyAttributeReadOnly else js.JSPropertyAttributes.kJSPropertyAttributeNone,
};
+
+ count += 1;
}
// if (singleton) {
@@ -402,7 +579,7 @@ pub fn NewClass(
static_properties[i].getProperty = StaticProperty(i).getter;
const field = comptime @field(properties, property_names[i]);
- const hasSetter = std.meta.trait.hasField("set");
+
if (comptime hasSetter(@TypeOf(field))) {
static_properties[i].setProperty = StaticProperty(i).setter;
}
@@ -415,8 +592,6 @@ pub fn NewClass(
def.className = class_name_str;
// def.getProperty = getPropertyCallback;
- def.finalize
-
return def;
}
};
@@ -437,20 +612,18 @@ pub fn JSError(
exception.* = js.JSObjectMakeError(ctx, 1, &error_args, null);
} else {
var buf = std.fmt.allocPrintZ(allocator, fmt, args) catch unreachable;
+ defer allocator.free(buf);
+
var message = js.JSStringCreateWithUTF8CString(buf);
defer js.JSStringRelease(message);
- defer allocator.free(buf);
+
error_args[0] = js.JSValueMakeString(ctx, message);
exception.* = js.JSObjectMakeError(ctx, 1, &error_args, null);
}
}
pub fn getAllocator(ctx: js.JSContextRef) *std.mem.Allocator {
- var global_obj = js.JSContextGetGlobalObject(ctx);
- var priv = js.JSObjectGetPrivate(global_obj).?;
- var global = @ptrCast(*javascript.GlobalObject, @alignCast(@alignOf(*javascript.GlobalObject), priv));
-
- return global.vm.allocator;
+ return std.heap.c_allocator;
}
pub const JSStringList = std.ArrayList(js.JSStringRef);
@@ -465,3 +638,10 @@ pub const ArrayBuffer = struct {
typed_array_type: js.JSTypedArrayType,
};
+
+pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type {
+ return @ptrCast(
+ *Type,
+ @alignCast(@alignOf(*Type), js.JSObjectGetPrivate(obj).?),
+ );
+}
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 52dcfff1c..3e6d4c185 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -14,6 +14,7 @@ const http = @import("../../http.zig");
usingnamespace @import("./node_env_buf_map.zig");
usingnamespace @import("./base.zig");
+usingnamespace @import("./webcore/response.zig");
const DefaultSpeedyDefines = struct {
pub const Keys = struct {
@@ -138,6 +139,15 @@ pub const VirtualMachine = struct {
log: *logger.Log,
watcher: ?*http.Watcher = null,
+ event_listeners: EventListenerMixin.Map,
+
+ pub threadlocal var instance: *VirtualMachine = undefined;
+
+ pub fn deinit(vm: *VirtualMachine) void {
+ js.JSGlobalContextRelease(vm.ctx);
+ js.JSContextGroupRelease(vm.group);
+ }
+
pub fn init(
allocator: *std.mem.Allocator,
_args: Api.TransformOptions,
@@ -167,6 +177,8 @@ pub const VirtualMachine = struct {
.log = log,
.group = group,
+ .event_listeners = EventListenerMixin.Map.init(allocator),
+
.require_cache = RequireCacheType.init(allocator),
.global = global,
};
@@ -191,8 +203,6 @@ pub const VirtualMachine = struct {
}
};
-
-
pub const Object = struct {
ref: js.jsObjectRef,
};
@@ -525,13 +535,41 @@ pub const Module = struct {
size += this_size;
longest_size = std.math.max(this_size, longest_size);
}
- var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len + 2);
- static_properties[static_properties.len - 2] = js.JSStaticValue{
+ var static_properties = try vm.allocator.alloc(js.JSStaticValue, bundle.bundle.modules.len + 1 + GlobalObject.GlobalClass.static_value_count);
+ var copied_static_values = static_properties[bundle.bundle.modules.len..];
+ copied_static_values = copied_static_values[0 .. copied_static_values.len - 1];
+
+ copied_static_values[0] = js.JSStaticValue{
.name = Properties.UTF8.console[0.. :0],
.getProperty = getConsole,
.setProperty = null,
.attributes = .kJSPropertyAttributeNone,
};
+
+ copied_static_values[1] = js.JSStaticValue{
+ .name = Response.Class.definition.className,
+ .getProperty = Response.Class.RawGetter(NodeModuleList).getter,
+ .setProperty = null,
+ .attributes = .kJSPropertyAttributeNone,
+ };
+
+ copied_static_values[2] = js.JSStaticValue{
+ .name = Headers.Class.definition.className,
+ .getProperty = Headers.Class.RawGetter(NodeModuleList).getter,
+ .setProperty = null,
+ .attributes = .kJSPropertyAttributeNone,
+ };
+
+ copied_static_values[3] = js.JSStaticValue{
+ .name = Request.Class.definition.className,
+ .getProperty = Request.Class.RawGetter(NodeModuleList).getter,
+ .setProperty = null,
+ .attributes = .kJSPropertyAttributeNone,
+ };
+
+ // copied_static_values must match GlobalObject.GlobalClass.static_value_count
+ std.debug.assert(copied_static_values.len == 4);
+
static_properties[static_properties.len - 1] = std.mem.zeroes(js.JSStaticValue);
var utf8 = try vm.allocator.alloc(u8, size + std.math.max(longest_size, 32));
std.mem.set(u8, utf8, 0);
@@ -571,6 +609,7 @@ pub const Module = struct {
var node_module_global_class_def = js.kJSClassDefinitionEmpty;
node_module_global_class_def.staticValues = static_properties.ptr;
node_module_global_class_def.className = node_module_global_class_name[0.. :0];
+ node_module_global_class_def.staticFunctions = GlobalObject.GlobalClass.definition.staticFunctions;
// node_module_global_class_def.parentClass = vm.global.global_class;
var property_getters = try vm.allocator.alloc(js.JSObjectRef, bundle.bundle.modules.len);
@@ -1145,6 +1184,142 @@ pub const Module = struct {
pub const RequireObject = struct {};
};
+pub const EventListenerMixin = struct {
+ threadlocal var event_listener_names_buf: [128]u8 = undefined;
+ pub const List = std.ArrayList(js.JSObjectRef);
+ pub const Map = std.AutoHashMap(EventListenerMixin.EventType, EventListenerMixin.List);
+
+ pub const EventType = enum {
+ fetch,
+
+ pub fn match(str: string) ?EventType {
+ if (strings.eqlComptime(str, "fetch")) {
+ return EventType.fetch;
+ }
+
+ return null;
+ }
+ };
+
+ pub fn emitFetchEventError(
+ request: *http.RequestContext,
+ comptime fmt: string,
+ args: anytype,
+ ) void {
+ Output.prettyErrorln(fmt, args);
+ request.sendInternalError(error.FetchEventError) catch {};
+ }
+
+ pub fn emitFetchEvent(
+ vm: *VirtualMachine,
+ request_context: *http.RequestContext,
+ ) !void {
+ var listeners = vm.event_listeners.get(EventType.fetch) orelse return emitFetchEventError(
+ request_context,
+ "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?",
+ .{},
+ );
+ if (listeners.items.len == 0) return emitFetchEventError(
+ request_context,
+ "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?",
+ .{},
+ );
+
+ var exception: js.JSValueRef = null;
+ var fetch_event = try vm.allocator.create(FetchEvent);
+ fetch_event.* = FetchEvent{
+ .request_context = request_context,
+ .request = Request{ .request_context = request_context },
+ };
+
+ var fetch_args: [1]js.JSObjectRef = undefined;
+ for (listeners.items) |listener| {
+ fetch_args[0] = js.JSObjectMake(
+ vm.ctx,
+ FetchEvent.Class.get().*,
+ fetch_event,
+ );
+
+ _ = js.JSObjectCallAsFunction(
+ vm.ctx,
+ listener,
+ js.JSContextGetGlobalObject(vm.ctx),
+ 1,
+ &fetch_args,
+ &exception,
+ );
+ if (request_context.has_called_done) {
+ break;
+ }
+ }
+
+ if (exception != null) {
+ var message = js.JSValueToStringCopy(vm.ctx, exception, null);
+ defer js.JSStringRelease(message);
+ var buf = vm.allocator.alloc(u8, js.JSStringGetLength(message) + 1) catch unreachable;
+ defer vm.allocator.free(buf);
+ var note = buf[0 .. js.JSStringGetUTF8CString(message, buf.ptr, buf.len) - 1];
+
+ Output.prettyErrorln("<r><red>error<r>: <b>{s}<r>", .{note});
+ Output.flush();
+
+ if (!request_context.has_called_done) {
+ request_context.sendInternalError(error.JavaScriptError) catch {};
+ }
+ return;
+ }
+
+ if (!request_context.has_called_done) {
+ return emitFetchEventError(
+ request_context,
+ "\"fetch\" handler never called event.respondWith()",
+ .{},
+ );
+ }
+ }
+
+ pub fn addEventListener(
+ comptime Struct: type,
+ ) type {
+ const Handler = struct {
+ pub fn addListener(
+ ptr: *Struct,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (arguments.len == 0 or arguments.len == 1 or !js.JSValueIsString(ctx, arguments[0]) or !js.JSValueIsObject(ctx, arguments[arguments.len - 1]) or !js.JSObjectIsFunction(ctx, arguments[arguments.len - 1])) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ const name_len = js.JSStringGetLength(arguments[0]);
+ if (name_len > event_listener_names_buf.len) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ const name_used_len = js.JSStringGetUTF8CString(arguments[0], &event_listener_names_buf, event_listener_names_buf.len);
+ const name = event_listener_names_buf[0 .. name_used_len - 1];
+ const event = EventType.match(name) orelse return js.JSValueMakeUndefined(ctx);
+ var entry = VirtualMachine.instance.event_listeners.getOrPut(event) catch unreachable;
+
+ if (!entry.found_existing) {
+ entry.value_ptr.* = List.initCapacity(VirtualMachine.instance.allocator, 1) catch unreachable;
+ }
+
+ var callback = arguments[arguments.len - 1];
+ js.JSValueProtect(ctx, callback);
+ entry.value_ptr.append(callback) catch unreachable;
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+ };
+
+ return Handler;
+ }
+};
+
pub const GlobalObject = struct {
ref: js.JSObjectRef = undefined,
vm: *VirtualMachine,
@@ -1176,9 +1351,14 @@ pub const GlobalObject = struct {
pub const GlobalClass = NewClass(
GlobalObject,
"Global",
- .{},
+ .{
+ .@"addEventListener" = EventListenerMixin.addEventListener(GlobalObject).addListener,
+ },
.{
.@"console" = getConsole,
+ .@"Request" = Request.Class.GetClass(GlobalObject).getter,
+ .@"Response" = Response.Class.GetClass(GlobalObject).getter,
+ .@"Headers" = Headers.Class.GetClass(GlobalObject).getter,
},
false,
false,
@@ -1195,18 +1375,11 @@ pub const GlobalObject = struct {
// js.JSValueProtect(js.JSContextGetGlobalContext(ctx), global.console);
// }
- return js.JSObjectMake(js.JSContextGetGlobalContext(ctx), global.console_class, global);
+ return js.JSObjectMake(js.JSContextGetGlobalContext(ctx), ConsoleClass.get().*, global);
}
pub fn boot(global: *GlobalObject) !void {
- global.console_definition = ConsoleClass.define();
- global.console_class = js.JSClassRetain(js.JSClassCreate(&global.console_definition));
-
- global.global_class_def = GlobalClass.define();
- global.global_class = js.JSClassRetain(js.JSClassCreate(&global.global_class_def));
-
- global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, global.global_class));
-
+ global.ctx = js.JSGlobalContextRetain(js.JSGlobalContextCreateInGroup(global.vm.group, GlobalObject.GlobalClass.get().*));
std.debug.assert(js.JSObjectSetPrivate(js.JSContextGetGlobalObject(global.ctx), global));
if (!printer_buf_loaded) {
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index 580714da8..d65ac0732 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -2,7 +2,7 @@ usingnamespace @import("../base.zig");
const std = @import("std");
const Api = @import("../../../api/schema.zig").Api;
const http = @import("../../../http.zig");
-
+pub const JavaScript = @import("../javascript.zig");
pub const Response = struct {
pub const Class = NewClass(
Response,
@@ -28,17 +28,6 @@ pub const Response = struct {
false,
);
- pub var class_definition: js.JSClassDefinition = undefined;
- pub var class_ref: js.JSClassRef = undefined;
- pub var loaded = false;
-
- pub fn load() void {
- if (!loaded) {
- class_definition = Class.define();
- class_ref = js.JSClassRetain(js.JSClassCreate(&class_definition));
- loaded = true;
- }
- }
allocator: *std.mem.Allocator,
body: Body,
@@ -68,22 +57,50 @@ pub const Response = struct {
pub fn finalize(
this: *Response,
- ctx: js.JSContextRef,
+ ctx: js.JSObjectRef,
) void {
this.body.deinit(this.allocator);
this.allocator.destroy(this);
}
+ pub fn mimeType(response: *const Response, request_ctx: *const http.RequestContext) string {
+ if (response.body.init.headers) |headers| {
+ // Remember, we always lowercase it
+ // hopefully doesn't matter here tho
+ if (headers.getHeaderIndex("content-type")) |content_type| {
+ return headers.asStr(headers.entries.items(.value)[content_type]);
+ }
+ }
+
+ if (request_ctx.url.extname.len > 0) {
+ return http.MimeType.byExtension(request_ctx.url.extname).value;
+ }
+
+ switch (response.body.value) {
+ .Empty => {
+ return "text/plain";
+ },
+ .String => |body| {
+ // poor man's mimetype sniffing
+ if (body.len > 0 and (body[0] == '{' or body[0] == '[')) {
+ return http.MimeType.json.value;
+ }
+
+ return http.MimeType.html.value;
+ },
+ .ArrayBuffer => {
+ return "application/octet-stream";
+ },
+ }
+ }
+
pub fn constructor(
ctx: js.JSContextRef,
function: js.JSObjectRef,
- arguments_len: usize,
- arguments_ptr: [*c]const js.JSValueRef,
+ arguments: []const js.JSValueRef,
exception: js.ExceptionRef,
- ) callconv(.C) js.JSObjectRef {
- const arguments = arguments_ptr[0..arguments_len];
-
- const body = brk: {
+ ) js.JSObjectRef {
+ const body: Body = brk: {
switch (arguments.len) {
0 => {
break :brk Body.@"404"(ctx);
@@ -102,65 +119,431 @@ pub const Response = struct {
unreachable;
};
- if (exception != null) {
- return null;
- }
+ // if (exception != null) {
+ // return null;
+ // }
- var allocator = getAllocator(ctx);
- var response = allocator.create(Response) catch return null;
+ var response = getAllocator(ctx).create(Response) catch return null;
response.* = Response{
.body = body,
- .allocator = allocator,
+ .allocator = getAllocator(ctx),
};
- return js.JSObjectMake(ctx, class_ref, response);
+ return js.JSObjectMake(ctx, Response.Class.get().*, response);
}
};
+// https://developer.mozilla.org/en-US/docs/Web/API/Headers
pub const Headers = struct {
pub const Kv = struct {
- key: Api.StringPointer,
+ name: Api.StringPointer,
value: Api.StringPointer,
};
pub const Entries = std.MultiArrayList(Kv);
entries: Entries,
buf: std.ArrayListUnmanaged(u8),
allocator: *std.mem.Allocator,
- used: usize = 0,
+ used: u32 = 0,
+ guard: Guard = Guard.none,
+
+ pub fn deinit(
+ headers: *Headers,
+ ) void {
+ headers.buf.deinit(headers.allocator);
+ headers.entries.deinit(headers.allocator);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Headers#methods
+ pub const JS = struct {
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
+ pub fn get(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (arguments.len == 0 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) {
+ return js.JSValueMakeNull(ctx);
+ }
+
+ const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len);
+ const key = header_kv_buf[0 .. key_len - 1];
+ if (this.getHeaderIndex(key)) |index| {
+ var str = this.asStr(this.entries.items(.value)[index]);
+ var ref = js.JSStringCreateWithUTF8CString(str.ptr);
+ defer js.JSStringRelease(ref);
+ return js.JSValueMakeString(ctx, ref);
+ } else {
+ return js.JSValueMakeNull(ctx);
+ }
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Headers/set
+ // > The difference between set() and Headers.append is that if the specified header already exists and accepts multiple values
+ // > set() overwrites the existing value with the new one, whereas Headers.append appends the new value to the end of the set of values.
+ pub fn set(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ this.putHeader(arguments[0], arguments[1], false);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Headers/append
+ pub fn append(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ this.putHeader(arguments[0], arguments[1], true);
+ return js.JSValueMakeUndefined(ctx);
+ }
+ pub fn delete(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.guard == .request or arguments.len < 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) {
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len) - 1;
+ const key = header_kv_buf[0..key_len];
+
+ if (this.getHeaderIndex(key)) |header_i| {
+ this.entries.orderedRemove(header_i);
+ }
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+ pub fn entries(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ Output.prettyErrorln("<r><b>Headers.entries()<r> is not implemented yet - sorry!!", .{});
+ return js.JSValueMakeNull(ctx);
+ }
+ pub fn keys(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ Output.prettyErrorln("H<r><b>eaders.keys()<r> is not implemented yet- sorry!!", .{});
+ return js.JSValueMakeNull(ctx);
+ }
+ pub fn values(
+ this: *Headers,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ Output.prettyErrorln("<r><b>Headers.values()<r> is not implemented yet - sorry!!", .{});
+ return js.JSValueMakeNull(ctx);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSObjectRef {
+ var headers = getAllocator(ctx).create(Headers) catch unreachable;
+ if (arguments.len > 0 and js.JSValueIsObjectOfClass(ctx, arguments[0], Headers.Class.get().*)) {
+ var other = castObj(arguments[0], Headers);
+ other.clone(headers) catch unreachable;
+ } else {
+ headers.* = Headers{
+ .entries = @TypeOf(headers.entries){},
+ .buf = @TypeOf(headers.buf){},
+ .used = 0,
+ .allocator = getAllocator(ctx),
+ .guard = Guard.none,
+ };
+ }
+
+ return js.JSObjectMake(ctx, Headers.Class.get().*, headers);
+ }
+
+ pub fn finalize(
+ this: *Headers,
+ ctx: js.JSObjectRef,
+ ) void {
+ this.deinit();
+ }
+ };
+ pub const Class = NewClass(
+ Headers,
+ "Headers",
+ .{
+ .@"get" = JS.get,
+ .@"set" = JS.set,
+ .@"append" = JS.append,
+ .@"delete" = JS.delete,
+ .@"entries" = JS.entries,
+ .@"keys" = JS.keys,
+ .@"values" = JS.values,
+ .@"constructor" = JS.constructor,
+ .@"finalize" = JS.finalize,
+ },
+ .{},
+ true,
+ false,
+ );
+
+ // https://developer.mozilla.org/en-US/docs/Glossary/Guard
+ pub const Guard = enum {
+ immutable,
+ request,
+ @"request-no-cors",
+ response,
+ none,
+ };
+
+ // TODO: is it worth making this lazy? instead of copying all the request headers, should we just do it on get/put/iterator?
+ pub fn fromRequestCtx(allocator: *std.mem.Allocator, request: *http.RequestContext) !Headers {
+ var total_len: usize = 0;
+ for (request.request.headers) |header| {
+ total_len += header.name.len;
+ total_len += header.value.len;
+ }
+ // for the null bytes
+ total_len += request.request.headers.len * 2;
+ var headers = Headers{
+ .allocator = allocator,
+ .entries = Entries{},
+ .buf = std.ArrayListUnmanaged(u8){},
+ };
+ try headers.entries.ensureTotalCapacity(allocator, request.request.headers.len);
+ try headers.buf.ensureTotalCapacity(allocator, total_len);
+ headers.buf.expandToCapacity();
+ headers.guard = Guard.request;
- fn appendString(this: *Headers, str: js.JSStringRef, comptime needs_lowercase: bool) Api.StringPointer {
- const ptr = Api.StringPointer{ .offset = this.used, .length = js.JSStringGetLength(str) };
+ for (request.request.headers) |header| {
+ headers.entries.appendAssumeCapacity(Kv{
+ .name = headers.appendString(
+ string,
+ header.name,
+ true,
+ true,
+ true,
+ ),
+ .value = headers.appendString(
+ string,
+ header.value,
+ true,
+ true,
+ true,
+ ),
+ });
+ }
+
+ headers.guard = Guard.immutable;
+
+ return headers;
+ }
+
+ pub fn asStr(headers: *const Headers, ptr: Api.StringPointer) []u8 {
+ return headers.buf.items[ptr.offset..][0..ptr.length];
+ }
+
+ threadlocal var header_kv_buf: [4096]u8 = undefined;
+
+ pub fn putHeader(headers: *Headers, key_: js.JSStringRef, value_: js.JSStringRef, comptime append: bool) void {
+ const key_len = js.JSStringGetUTF8CString(key_, &header_kv_buf, header_kv_buf.len) - 1;
+ // TODO: make this one pass instead of two
+ var key = strings.trim(header_kv_buf[0..key_len], " \n\r");
+ key = std.ascii.lowerString(key[0..key.len], key);
+
+ var remainder = header_kv_buf[key.len..];
+ const value_len = js.JSStringGetUTF8CString(value_, remainder.ptr, remainder.len) - 1;
+ var value = strings.trim(remainder[0..value_len], " \n\r");
+
+ if (headers.getHeaderIndex(key)) |header_i| {
+ const existing_value = headers.entries.items(.value)[header_i];
+
+ if (append) {
+ const end = @truncate(u32, value.len + existing_value.length + 2);
+ headers.buf.ensureUnusedCapacity(headers.allocator, end) catch unreachable;
+ headers.buf.expandToCapacity();
+ var new_end = headers.buf.items[headers.used..][0 .. end - 1];
+ const existing_buf = headers.asStr(existing_value);
+ std.mem.copy(u8, existing_buf, new_end);
+ new_end[existing_buf.len] = ',';
+ std.mem.copy(u8, new_end[existing_buf.len + 1 ..], value);
+ new_end.ptr[end - 1] = 0;
+ headers.entries.items(.value)[header_i] = Api.StringPointer{ .offset = headers.used, .length = end - 1 };
+ headers.used += end;
+ // Can we get away with just overwriting in-place?
+ } else if (existing_value.length < value.len) {
+ std.mem.copy(u8, headers.asStr(existing_value), value);
+ headers.entries.items(.value)[header_i].length = @truncate(u32, value.len);
+ headers.asStr(headers.entries.items(.value)[header_i]).ptr[value.len] = 0;
+ // Otherwise, append to the buffer, and just don't bother dealing with the existing header value
+ // We assume that these header objects are going to be kind of short-lived.
+ } else {
+ headers.buf.ensureUnusedCapacity(headers.allocator, value.len + 1) catch unreachable;
+ headers.buf.expandToCapacity();
+ headers.entries.items(.value)[header_i] = headers.appendString(string, value, false, true, true);
+ }
+ } else {
+ headers.appendHeader(key, value, false, false, true);
+ }
+ }
+
+ pub fn getHeaderIndex(headers: *const Headers, key: string) ?u32 {
+ for (headers.entries.items(.name)) |name, i| {
+ if (name.length == key.len and strings.eqlInsensitive(key, headers.asStr(name))) {
+ return @truncate(u32, i);
+ }
+ }
+
+ return null;
+ }
+
+ pub fn appendHeader(
+ headers: *Headers,
+ key: string,
+ value: string,
+ comptime needs_lowercase: bool,
+ comptime needs_normalize: bool,
+ comptime append_null: bool,
+ ) void {
+ headers.buf.ensureUnusedCapacity(headers.allocator, key.len + value.len + 2) catch unreachable;
+ headers.buf.expandToCapacity();
+ headers.entries.append(
+ headers.allocator,
+ Kv{
+ .name = headers.appendString(
+ string,
+ key,
+ needs_lowercase,
+ needs_normalize,
+ append_null,
+ ),
+ .value = headers.appendString(
+ string,
+ key,
+ needs_lowercase,
+ needs_normalize,
+ append_null,
+ ),
+ },
+ ) catch unreachable;
+ }
+
+ fn appendString(
+ this: *Headers,
+ comptime StringType: type,
+ str: StringType,
+ comptime needs_lowercase: bool,
+ comptime needs_normalize: bool,
+ comptime append_null: bool,
+ ) Api.StringPointer {
+ var ptr = Api.StringPointer{ .offset = this.used, .length = 0 };
+ ptr.length = @truncate(
+ u32,
+ switch (comptime StringType) {
+ js.JSStringRef => js.JSStringGetLength(str),
+ else => str.len,
+ },
+ );
std.debug.assert(ptr.length > 0);
+
std.debug.assert(this.buf.items.len >= ptr.offset + ptr.length);
- var slice = this.buf.items[ptr.offset][0..ptr.length];
- ptr.length = js.JSStringGetUTF8CString(this, slice.ptr, slice.len);
- if (needs_lowercase) {
+ var slice = this.buf.items[ptr.offset..][0..ptr.length];
+ switch (comptime StringType) {
+ js.JSStringRef => {
+ ptr.length = @truncate(u32, js.JSStringGetUTF8CString(str, slice.ptr, slice.len) - 1);
+ },
+ else => {
+ std.mem.copy(u8, slice, str);
+ },
+ }
+
+ if (comptime needs_normalize) {
+ slice = strings.trim(slice, " \r\n");
+ }
+
+ if (comptime needs_lowercase) {
for (slice) |c, i| {
slice[i] = std.ascii.toLower(c);
}
}
+ if (comptime append_null) {
+ slice.ptr[slice.len] = 0;
+ this.used += 1;
+ }
- this.used += ptr.len;
+ ptr.length = @truncate(u32, slice.len);
+ this.used += @truncate(u32, ptr.length);
return ptr;
}
fn appendNumber(this: *Headers, num: f64) Api.StringPointer {
- const ptr = Api.StringPointer{ .offset = this.used, .length = std.fmt.count("{d}", num) };
+ var ptr = Api.StringPointer{ .offset = this.used, .length = @truncate(
+ u32,
+ std.fmt.count("{d}", .{num}),
+ ) };
std.debug.assert(this.buf.items.len >= ptr.offset + ptr.length);
- var slice = this.buf.items[ptr.offset][0..ptr.length];
- ptr.length = std.fmt.bufPrint(slice, "{d}", num) catch 0;
- this.used += ptr.len;
+ var slice = this.buf.items[ptr.offset..][0..ptr.length];
+ var buf = std.fmt.bufPrint(slice, "{d}", .{num}) catch &[_]u8{};
+ ptr.length = @truncate(u32, buf.len);
+ this.used += ptr.length;
return ptr;
}
- pub fn append(this: *Headers, ctx: js.JSContextRef, key: js.JSStringRef, comptime value_type: js.JSType, value: js.JSValueRef) !void {
+ pub fn appendInit(this: *Headers, ctx: js.JSContextRef, key: js.JSStringRef, comptime value_type: js.JSType, value: js.JSValueRef) !void {
this.entries.append(this.allocator, Kv{
- .key = this.appendString(key, true),
+ .name = this.appendString(js.JSStringRef, key, true, true, false),
.value = switch (comptime value_type) {
js.JSType.kJSTypeNumber => this.appendNumber(js.JSValueToNumber(ctx, value, null)),
- js.JSType.kJSTypeString => this.appendString(value, false),
+ js.JSType.kJSTypeString => this.appendString(js.JSStringRef, value, true, true, false),
+ else => unreachable,
},
- });
+ }) catch unreachable;
+ }
+
+ pub fn clone(this: *Headers, to: *Headers) !void {
+ to.* = Headers{
+ .entries = try this.entries.clone(this.allocator),
+ .buf = try @TypeOf(this.buf).initCapacity(this.allocator, this.buf.items.len),
+ .used = this.used,
+ .allocator = this.allocator,
+ .guard = Guard.none,
+ };
+ to.buf.expandToCapacity();
+ std.mem.copy(u8, to.buf.items, this.buf.items);
}
};
@@ -171,8 +554,7 @@ pub const Body = struct {
pub fn deinit(this: *Body, allocator: *std.mem.Allocator) void {
if (this.init.headers) |headers| {
- headers.buf.deinit(headers.allocator);
- headers.entries.deinit(headers.allocator);
+ headers.deinit();
}
switch (this.value) {
@@ -218,7 +600,7 @@ pub const Body = struct {
var estimated_buffer_len: usize = 0;
var j: usize = 0;
while (j < total_header_count) : (j += 1) {
- var key_ref = js.JSPropertyNameArrayGetNameAtIndex(j);
+ var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j);
var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null);
switch (js.JSValueGetType(ctx, value_ref)) {
@@ -231,6 +613,7 @@ pub const Body = struct {
}
},
js.JSType.kJSTypeString => {
+ const key_len = js.JSStringGetLength(key_ref);
const value_len = js.JSStringGetLength(value_ref);
if (key_len > 0 and value_len > 0) {
real_header_count += 1;
@@ -245,24 +628,25 @@ pub const Body = struct {
j = 0;
var headers = Headers{
- .buf = try std.ArrayList(u8).initCapacity(allocator, estimated_buffer_len),
- .entries = std.mem.zeroes(Headers.Entries),
+ .allocator = allocator,
+ .buf = try std.ArrayListUnmanaged(u8).initCapacity(allocator, estimated_buffer_len),
+ .entries = Headers.Entries{},
};
errdefer headers.deinit();
try headers.entries.ensureTotalCapacity(allocator, real_header_count);
while (j < total_header_count) : (j += 1) {
- var key_ref = js.JSPropertyNameArrayGetNameAtIndex(j);
+ var key_ref = js.JSPropertyNameArrayGetNameAtIndex(header_keys, j);
var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null);
switch (js.JSValueGetType(ctx, value_ref)) {
js.JSType.kJSTypeNumber => {
if (js.JSStringGetLength(key_ref) == 0) continue;
- try headers.append(ctx, key_ref, .kJSTypeNumber, value_ref);
+ try headers.appendInit(ctx, key_ref, .kJSTypeNumber, value_ref);
},
js.JSType.kJSTypeString => {
if (js.JSStringGetLength(value_ref) == 0 or js.JSStringGetLength(key_ref) == 0) continue;
- try headers.append(ctx, key_ref, .kJSTypeString, value_ref);
+ try headers.appendInit(ctx, key_ref, .kJSTypeString, value_ref);
},
else => {},
}
@@ -276,7 +660,7 @@ pub const Body = struct {
},
"statusCode".len => {
if (js.JSStringIsEqualToUTF8CString(property_name_ref, "statusCode")) {
- var value_ref = js.JSObjectGetProperty(ctx, header_prop, key_ref, null);
+ var value_ref = js.JSObjectGetProperty(ctx, init_ref, property_name_ref, null);
var exception: js.JSValueRef = null;
const number = js.JSValueToNumber(ctx, value_ref, &exception);
if (exception != null or !std.math.isFinite(number)) continue;
@@ -300,6 +684,20 @@ pub const Body = struct {
String,
Empty,
};
+
+ pub fn length(value: *const Value) usize {
+ switch (value.*) {
+ .ArrayBuffer => |buf| {
+ return buf.ptr[buf.offset..buf.byte_len].len;
+ },
+ .String => |str| {
+ return str.len;
+ },
+ .Empty => {
+ return 0;
+ },
+ }
+ }
};
pub fn @"404"(ctx: js.JSContextRef) Body {
@@ -309,12 +707,24 @@ pub const Body = struct {
}, .value = .{ .Empty = 0 } };
}
- pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: ExceptionValueRef) Body {
- return extractBody(ctx, body_ref, false, null);
+ pub fn extract(ctx: js.JSContextRef, body_ref: js.JSObjectRef, exception: js.ExceptionRef) Body {
+ return extractBody(
+ ctx,
+ body_ref,
+ false,
+ null,
+ exception,
+ );
}
- pub fn extractWithInit(ctx: js.JSContextRef, body_ref: js.JSObjectRef, init_ref: js.JSValueRef, exception: ExceptionValueRef) Body {
- return extractBody(ctx, body_ref, true, init_ref);
+ pub fn extractWithInit(ctx: js.JSContextRef, body_ref: js.JSObjectRef, init_ref: js.JSValueRef, exception: js.ExceptionRef) Body {
+ return extractBody(
+ ctx,
+ body_ref,
+ true,
+ init_ref,
+ exception,
+ );
}
// https://github.com/WebKit/webkit/blob/main/Source/WebCore/Modules/fetch/FetchBody.cpp#L45
@@ -323,49 +733,58 @@ pub const Body = struct {
body_ref: js.JSObjectRef,
comptime has_init: bool,
init_ref: js.JSValueRef,
- exception: ExceptionValueRef,
+ exception: js.ExceptionRef,
) Body {
var body = Body{ .init = Init{ .headers = null, .status_code = 200 }, .value = .{ .Empty = 0 } };
switch (js.JSValueGetType(ctx, body_ref)) {
- js.kJSTypeString => {
- if (exception == null) {
- var allocator = getAllocator(ctx);
-
- if (has_init) {
- body.init = Init.init(allocator, ctx, init_ref.?) catch unreachable;
- }
- const len = js.JSStringGetLength(body_ref);
- if (len == 0) {
- body.value = .{ .String = "" };
- return body;
- }
+ .kJSTypeString => {
+ var allocator = getAllocator(ctx);
- var str = try allocator.alloc(u8, len);
+ if (comptime has_init) {
+ if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| {
+ if (maybeInit) |init_| {
+ body.init = init_;
+ }
+ } else |err| {}
+ }
- body.value = Value{ .String = str[0..js.JSStringGetUTF8CString(body_ref, str.ptr, len)] };
+ var str_ref = js.JSValueToStringCopy(ctx, body_ref, exception);
+ defer js.JSStringRelease(str_ref);
+ const len = js.JSStringGetMaximumUTF8CStringSize(str_ref);
+ if (len == 0) {
+ body.value = .{ .String = "" };
return body;
}
+
+ var str = allocator.alloc(u8, len + 1) catch unreachable;
+
+ body.value = Value{ .String = str[0 .. js.JSStringGetUTF8CString(str_ref, str.ptr, len) - 1] };
+ return body;
},
- js.kJSTypeObject => {
+ .kJSTypeObject => {
const typed_array = js.JSValueGetTypedArrayType(ctx, body_ref, exception);
switch (typed_array) {
js.JSTypedArrayType.kJSTypedArrayTypeNone => {},
else => {
const buffer = ArrayBuffer{
- .ptr = js.JSObjectGetTypedArrayBytesPtr(ctx, body_ref, exception),
- .offset = js.JSObjectGetTypedArrayByteOffset(ctx, body_ref, exception),
- .len = js.JSObjectGetTypedArrayLength(ctx, body_ref, exception),
- .byte_len = js.JSObjectGetTypedArrayLength(ctx, body_ref, exception),
+ .ptr = @ptrCast([*]u8, js.JSObjectGetTypedArrayBytesPtr(ctx, body_ref.?, exception).?),
+ .offset = @truncate(u32, js.JSObjectGetTypedArrayByteOffset(ctx, body_ref.?, exception)),
+ .len = @truncate(u32, js.JSObjectGetTypedArrayLength(ctx, body_ref.?, exception)),
+ .byte_len = @truncate(u32, js.JSObjectGetTypedArrayLength(ctx, body_ref.?, exception)),
.typed_array_type = typed_array,
};
- if (exception == null) {
- if (has_init) {
- body.init = Init.init(allocator, ctx, init_ref.?) catch unreachable;
- }
- body.value = Value{ .ArrayBuffer = buffer };
- return body;
+ var allocator = getAllocator(ctx);
+
+ if (comptime has_init) {
+ if (Init.init(allocator, ctx, init_ref.?)) |maybeInit| {
+ if (maybeInit) |init_| {
+ body.init = init_;
+ }
+ } else |err| {}
}
+ body.value = Value{ .ArrayBuffer = buffer };
+ return body;
},
}
},
@@ -373,27 +792,341 @@ pub const Body = struct {
}
if (exception == null) {
- JSError(getAllocator(allocator), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception);
+ JSError(getAllocator(ctx), "Body must be a string or a TypedArray (for now)", .{}, ctx, exception);
}
- return null;
+ return body;
}
};
+// https://developer.mozilla.org/en-US/docs/Web/API/Request
+pub const Request = struct {
+ request_context: *http.RequestContext,
+ url_string_ref: js.JSStringRef = null,
+ headers: ?Headers = null,
+
+ pub const Class = NewClass(
+ Request,
+ "Request",
+ .{},
+ .{
+ .@"cache" = .{
+ .@"get" = getCache,
+ .@"ro" = true,
+ },
+ .@"credentials" = .{
+ .@"get" = getCredentials,
+ .@"ro" = true,
+ },
+ .@"destination" = .{
+ .@"get" = getDestination,
+ .@"ro" = true,
+ },
+ .@"headers" = .{
+ .@"get" = getHeaders,
+ .@"ro" = true,
+ },
+ .@"integrity" = .{
+ .@"get" = getIntegrity,
+ .@"ro" = true,
+ },
+ .@"method" = .{
+ .@"get" = getMethod,
+ .@"ro" = true,
+ },
+ .@"mode" = .{
+ .@"get" = getMode,
+ .@"ro" = true,
+ },
+ .@"redirect" = .{
+ .@"get" = getRedirect,
+ .@"ro" = true,
+ },
+ .@"referrer" = .{
+ .@"get" = getReferrer,
+ .@"ro" = true,
+ },
+ .@"referrerPolicy" = .{
+ .@"get" = getReferrerPolicy,
+ .@"ro" = true,
+ },
+ .@"url" = .{
+ .@"get" = getUrl,
+ .@"ro" = true,
+ },
+ },
+ true,
+ false,
+ );
+
+ pub fn getCache(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.default);
+ }
+ pub fn getCredentials(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.include);
+ }
+ pub fn getDestination(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.empty_string);
+ }
+ pub fn getHeaders(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.headers == null) {
+ this.headers = Headers.fromRequestCtx(getAllocator(ctx), this.request_context) catch unreachable;
+ }
+
+ return js.JSObjectMake(ctx, Headers.Class.get().*, &this.headers.?);
+ }
+ pub fn getIntegrity(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.empty_string);
+ }
+ pub fn getMethod(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ const string_ref = switch (this.request_context.method) {
+ .GET => Properties.Refs.GET,
+ .HEAD => Properties.Refs.HEAD,
+ .PATCH => Properties.Refs.PATCH,
+ .PUT => Properties.Refs.PUT,
+ .POST => Properties.Refs.POST,
+ .OPTIONS => Properties.Refs.OPTIONS,
+ else => Properties.Refs.empty_string,
+ };
+ return js.JSValueMakeString(ctx, string_ref);
+ }
+
+ pub fn getMode(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.navigate);
+ }
+ pub fn getRedirect(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.follow);
+ }
+ pub fn getReferrer(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.empty_string);
+ }
+ pub fn getReferrerPolicy(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeString(ctx, Properties.Refs.empty_string);
+ }
+ pub fn getUrl(
+ this: *Request,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.url_string_ref == null) {
+ this.url_string_ref = js.JSStringCreateWithUTF8CString(this.request_context.getFullURL());
+ }
+
+ return js.JSValueMakeString(ctx, this.url_string_ref);
+ }
+};
+
+// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/workers/service/FetchEvent.h
pub const FetchEvent = struct {
started_waiting_at: u64 = 0,
response: ?*Response = null,
request_context: *http.RequestContext,
+ request: Request,
pub const Class = NewClass(
FetchEvent,
"FetchEvent",
.{ .@"respondWith" = respondWith, .@"waitUntil" = waitUntil },
.{
- .@"client" = getClient,
- .@"request" = getRequest,
+ .@"client" = .{ .@"get" = getClient, .ro = true },
+ .@"request" = .{ .@"get" = getRequest, .ro = true },
},
true,
false,
);
+
+ pub fn getClient(
+ this: *FetchEvent,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ Output.prettyErrorln("FetchEvent.client is not implemented yet - sorry!!", .{});
+ Output.flush();
+ return js.JSValueMakeUndefined(ctx);
+ }
+ pub fn getRequest(
+ this: *FetchEvent,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSObjectMake(ctx, Request.Class.get().*, &this.request);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith
+ pub fn respondWith(
+ this: *FetchEvent,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.request_context.has_called_done) return js.JSValueMakeUndefined(ctx);
+
+ // A Response or a Promise that resolves to a Response. Otherwise, a network error is returned to Fetch.
+ if (arguments.len == 0 or !Response.Class.loaded) {
+ JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception);
+ this.request_context.sendInternalError(error.respondWithWasEmpty) catch {};
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var arg = arguments[0];
+ if (!js.JSValueIsObjectOfClass(ctx, arg, Response.Class.ref)) {
+ JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception);
+ this.request_context.sendInternalError(error.respondWithWasEmpty) catch {};
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var ptr = js.JSObjectGetPrivate(arg);
+ if (ptr == null) {
+ JSError(getAllocator(ctx), "event.respondWith()'s Response object was invalid. This may be an internal error.", .{}, ctx, exception);
+ this.request_context.sendInternalError(error.respondWithWasInvalid) catch {};
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ var response = @ptrCast(*Response, @alignCast(@alignOf(*Response), ptr.?));
+
+ var needs_mime_type = true;
+ var content_length: ?usize = null;
+ if (response.body.init.headers) |*headers| {
+ this.request_context.clearHeaders() catch {};
+ var i: usize = 0;
+ while (i < headers.entries.len) : (i += 1) {
+ var header = headers.entries.get(i);
+ const name = headers.asStr(header.name);
+ if (strings.eqlComptime(name, "content-type")) {
+ needs_mime_type = false;
+ }
+
+ if (strings.eqlComptime(name, "content-length")) {
+ content_length = std.fmt.parseInt(usize, headers.asStr(header.value), 10) catch null;
+ continue;
+ }
+
+ this.request_context.appendHeaderSlow(
+ name,
+ headers.asStr(header.value),
+ ) catch unreachable;
+ }
+ }
+
+ if (needs_mime_type) {
+ this.request_context.appendHeader("Content-Type", response.mimeType(this.request_context));
+ }
+
+ const content_length_ = content_length orelse response.body.value.length();
+
+ if (content_length_ == 0) {
+ this.request_context.sendNoContent() catch return js.JSValueMakeUndefined(ctx);
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ if (FeatureFlags.strong_etags_for_built_files) {
+ switch (response.body.value) {
+ .ArrayBuffer => |buf| {
+ const did_send = this.request_context.writeETag(buf.ptr[buf.offset..buf.byte_len]) catch false;
+ if (did_send) return js.JSValueMakeUndefined(ctx);
+ },
+ .String => |str| {
+ const did_send = this.request_context.writeETag(str) catch false;
+ if (did_send) return js.JSValueMakeUndefined(ctx);
+ },
+ else => unreachable,
+ }
+ }
+
+ defer this.request_context.done();
+
+ this.request_context.writeStatusSlow(response.body.init.status_code) catch return js.JSValueMakeUndefined(ctx);
+ this.request_context.prepareToSendBody(content_length_, false) catch return js.JSValueMakeUndefined(ctx);
+
+ switch (response.body.value) {
+ .ArrayBuffer => |buf| {
+ this.request_context.writeBodyBuf(buf.ptr[buf.offset..buf.byte_len]) catch return js.JSValueMakeUndefined(ctx);
+ },
+ .String => |str| {
+ this.request_context.writeBodyBuf(str) catch return js.JSValueMakeUndefined(ctx);
+ },
+ else => unreachable,
+ }
+
+ return js.JSValueMakeUndefined(ctx);
+ }
+
+ pub fn waitUntil(
+ this: *FetchEvent,
+ ctx: js.JSContextRef,
+ function: js.JSObjectRef,
+ thisObject: js.JSObjectRef,
+ arguments: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ return js.JSValueMakeUndefined(ctx);
+ }
};
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index 03382e7a2..b65b142bf 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -367,6 +367,15 @@ pub fn containsNonBmpCodePoint(text: string) bool {
return false;
}
+// this is std.mem.trim except it doesn't forcibly change the slice to be const
+pub fn trim(slice: anytype, values_to_strip: []const u8) @TypeOf(slice) {
+ var begin: usize = 0;
+ var end: usize = slice.len;
+ while (begin < end and std.mem.indexOfScalar(u8, values_to_strip, slice[begin]) != null) : (begin += 1) {}
+ while (end > begin and std.mem.indexOfScalar(u8, values_to_strip, slice[end - 1]) != null) : (end -= 1) {}
+ return slice[begin..end];
+}
+
pub fn containsNonBmpCodePointUTF16(_text: JavascriptString) bool {
const n = _text.len;
if (n > 0) {
diff --git a/src/sync.zig b/src/sync.zig
index c43c227db..8b608757c 100644
--- a/src/sync.zig
+++ b/src/sync.zig
@@ -465,7 +465,7 @@ pub fn Channel(
if (self.is_closed)
return error.Closed;
- self.buffer.writeItem(item) catch |err| {
+ self.buffer.write(items) catch |err| {
if (buffer_type == .Dynamic)
return err;
break :blk false;