aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-13 02:01:41 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-08-13 02:01:41 -0700
commitf59892f647ceef1c05e40c9cdef4f79d0a530c2f (patch)
tree6fed07dc02e722de634618a6f2d246f52510d141
parent86642cbdd528bbf37708dbb30a815c9a68b6aea1 (diff)
downloadbun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.tar.gz
bun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.tar.zst
bun-f59892f647ceef1c05e40c9cdef4f79d0a530c2f.zip
late
Former-commit-id: 1d598bb05a3bac62d86063125e1fe2962f0b5cc6
-rw-r--r--demos/css-stress-test/framework.tsx38
-rw-r--r--demos/css-stress-test/next-env.d.ts4
-rw-r--r--demos/css-stress-test/nexty/client.development.tsx33
-rw-r--r--demos/css-stress-test/nexty/next-server.tsx0
-rw-r--r--demos/css-stress-test/nexty/render.tsx0
-rw-r--r--demos/css-stress-test/nexty/renderDocument.tsx568
-rw-r--r--demos/css-stress-test/nexty/server.development.tsx103
-rw-r--r--demos/css-stress-test/nexty/tsconfig.json23
-rw-r--r--src/bundler.zig10
-rw-r--r--src/css_scanner.zig23
-rw-r--r--src/http.zig4
-rw-r--r--src/http/url_path.zig72
-rw-r--r--src/javascript/jsc/api/router.zig46
-rw-r--r--src/javascript/jsc/base.zig4
-rw-r--r--src/javascript/jsc/bindings/ZigGlobalObject.cpp40
-rw-r--r--src/javascript/jsc/javascript.zig3
-rw-r--r--src/js_parser/js_parser.zig57
-rw-r--r--src/js_printer.zig34
-rw-r--r--src/options.zig34
-rw-r--r--src/runtime.js4
-rw-r--r--src/runtime.version2
21 files changed, 948 insertions, 154 deletions
diff --git a/demos/css-stress-test/framework.tsx b/demos/css-stress-test/framework.tsx
deleted file mode 100644
index 22b1a3109..000000000
--- a/demos/css-stress-test/framework.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import ReactDOMServer from "react-dom/server.browser";
-
-addEventListener("fetch", async (event: FetchEvent) => {
- var route = Wundle.match(event);
- console.log("Route", JSON.stringify(route.query, null, 2));
- const { default: PageComponent } = await import(route.filepath);
- // const router = Wundle.Router.match(event);
- // console.log("Route", router.name);
-
- // const { Base: Page } = await router.import();
-
- const response = new Response(`
- <!DOCTYPE html>
-<html>
- <head>
- <link rel="stylesheet" href="./src/index.css" />
- <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(
- <PageComponent />
- )}</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/next-env.d.ts b/demos/css-stress-test/next-env.d.ts
index 7b7aa2c77..9bc3dd46b 100644
--- a/demos/css-stress-test/next-env.d.ts
+++ b/demos/css-stress-test/next-env.d.ts
@@ -1,2 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/demos/css-stress-test/nexty/client.development.tsx b/demos/css-stress-test/nexty/client.development.tsx
index 1bc63a0c7..f15ccee28 100644
--- a/demos/css-stress-test/nexty/client.development.tsx
+++ b/demos/css-stress-test/nexty/client.development.tsx
@@ -1,5 +1,34 @@
-import ReactDOM from "react-dom";
+globalThis.process = {
+ platform: "posix",
+ env: {},
+};
+
+import * as ReactDOM from "react-dom";
+import App from "next/app";
export default function boot(EntryPointNamespace, loader) {
-
+ _boot(EntryPointNamespace);
+}
+
+function _boot(EntryPointNamespace) {
+ const next_data_node = document.querySelector("#__NEXT_DATA__");
+ if (!next_data_node) {
+ throw new Error(
+ "__NEXT_DATA__ is missing. That means something went wrong while rendering on the server."
+ );
+ }
+
+ try {
+ globalThis.NEXT_DATA = JSON.parse(next_data_node.innerHTML);
+ } catch (error) {
+ error.message = `Error parsing __NEXT_DATA__\n${error.message}`;
+ throw error;
+ }
+
+ const props = { ...globalThis.NEXT_DATA.props };
+ const PageComponent = EntryPointNamespace.default;
+ ReactDOM.hydrate(
+ <App Component={PageComponent} pageProps={props.pageProps}></App>,
+ document.querySelector("#__next")
+ );
}
diff --git a/demos/css-stress-test/nexty/next-server.tsx b/demos/css-stress-test/nexty/next-server.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/demos/css-stress-test/nexty/next-server.tsx
diff --git a/demos/css-stress-test/nexty/render.tsx b/demos/css-stress-test/nexty/render.tsx
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/demos/css-stress-test/nexty/render.tsx
diff --git a/demos/css-stress-test/nexty/renderDocument.tsx b/demos/css-stress-test/nexty/renderDocument.tsx
new file mode 100644
index 000000000..3ada60c86
--- /dev/null
+++ b/demos/css-stress-test/nexty/renderDocument.tsx
@@ -0,0 +1,568 @@
+import * as App from "next/app";
+import { AmpStateContext } from "next/dist/shared/lib/amp-context";
+import { HeadManagerContext } from "next/dist/shared/lib/head-manager-context";
+import Loadable from "next/dist/shared/lib/loadable";
+import { LoadableContext } from "next/dist/shared/lib/loadable-context";
+import { RouterContext } from "next/dist/shared/lib/router-context";
+import { NextRouter } from "next/dist/shared/lib/router/router";
+import {
+ AppType,
+ ComponentsEnhancer,
+ DocumentInitialProps,
+ DocumentProps,
+ DocumentType,
+ getDisplayName,
+ loadGetInitialProps,
+ NextComponentType,
+ RenderPage,
+ RenderPageResult,
+} from "next/dist/shared/lib/utils";
+import * as NextDocument from "next/document";
+import * as ReactDOMServer from "react-dom/server.browser";
+import * as url from "url";
+
+const dev = process.env.NODE_ENV === "development";
+
+type ParsedUrlQuery = Record<string, string | string[]>;
+
+const isJSFile = (file: string) =>
+ file.endsWith(".js") ||
+ file.endsWith(".mjs") ||
+ file.endsWith(".ts") ||
+ file.endsWith(".tsx");
+
+function getScripts(files: DocumentFiles) {
+ const { context, props } = this;
+ const {
+ assetPrefix,
+ buildManifest,
+ isDevelopment,
+ devOnlyCacheBusterQueryString,
+ disableOptimizedLoading,
+ } = context;
+ const normalScripts = files.allFiles.filter(isJSFile);
+ const lowPriorityScripts = buildManifest.lowPriorityFiles?.filter(isJSFile);
+
+ return [...normalScripts, ...lowPriorityScripts].map((file) => {
+ return (
+ <script
+ key={file}
+ src={`${assetPrefix}/_next/${encodeURI(
+ file
+ )}${devOnlyCacheBusterQueryString}`}
+ nonce={props.nonce}
+ async
+ crossOrigin={props.crossOrigin || process.env.__NEXT_CROSS_ORIGIN}
+ type="module"
+ />
+ );
+ });
+}
+
+interface DomainLocale {
+ defaultLocale: string;
+ domain: string;
+ http?: true;
+ locales?: string[];
+}
+
+function renderDocument(
+ Document: DocumentType,
+ {
+ buildManifest,
+ docComponentsRendered,
+ props,
+ docProps,
+ pathname,
+ query,
+ buildId,
+ canonicalBase,
+ assetPrefix,
+ runtimeConfig,
+ nextExport,
+ autoExport,
+ isFallback,
+ dynamicImportsIds,
+ dangerousAsPath,
+ err,
+ dev,
+ ampPath,
+ ampState,
+ inAmpMode,
+ hybridAmp,
+ dynamicImports,
+ headTags,
+ gsp,
+ gssp,
+ customServer,
+ gip,
+ appGip,
+ unstable_runtimeJS,
+ unstable_JsPreload,
+ devOnlyCacheBusterQueryString,
+ scriptLoader,
+ locale,
+ locales,
+ defaultLocale,
+ domainLocales,
+ isPreview,
+ disableOptimizedLoading,
+ }: RenderOpts & {
+ props: any;
+ docComponentsRendered: DocumentProps["docComponentsRendered"];
+ docProps: DocumentInitialProps;
+ pathname: string;
+ query: ParsedUrlQuery;
+ dangerousAsPath: string;
+ ampState: any;
+ ampPath: string;
+ inAmpMode: boolean;
+ hybridAmp: boolean;
+ dynamicImportsIds: (string | number)[];
+ dynamicImports: string[];
+ headTags: any;
+ isFallback?: boolean;
+ gsp?: boolean;
+ gssp?: boolean;
+ customServer?: boolean;
+ gip?: boolean;
+ appGip?: boolean;
+ devOnlyCacheBusterQueryString: string;
+ scriptLoader: any;
+ isPreview?: boolean;
+ autoExport?: boolean;
+ }
+): string {
+ return (
+ "<!DOCTYPE html>" +
+ ReactDOMServer.renderToStaticMarkup(
+ <AmpStateContext.Provider value={ampState}>
+ {Document.renderDocument(Document, {
+ __NEXT_DATA__: {
+ props, // The result of getInitialProps
+ page: pathname, // The rendered page
+ query, // querystring parsed / passed by the user
+ buildId, // buildId is used to facilitate caching of page bundles, we send it to the client so that pageloader knows where to load bundles
+ assetPrefix: assetPrefix === "" ? undefined : assetPrefix, // send assetPrefix to the client side when configured, otherwise don't sent in the resulting HTML
+ runtimeConfig, // runtimeConfig if provided, otherwise don't sent in the resulting HTML
+ nextExport, // If this is a page exported by `next export`
+ autoExport, // If this is an auto exported page
+ isFallback,
+ dynamicIds:
+ dynamicImportsIds.length === 0 ? undefined : dynamicImportsIds,
+ err: err || undefined, // err: err ? serializeError(dev, err) : undefined, // Error if one happened, otherwise don't sent in the resulting HTML
+ gsp, // whether the page is getStaticProps
+ gssp, // whether the page is getServerSideProps
+ customServer, // whether the user is using a custom server
+ gip, // whether the page has getInitialProps
+ appGip, // whether the _app has getInitialProps
+ locale,
+ locales,
+ defaultLocale,
+ domainLocales,
+ isPreview,
+ },
+ buildManifest,
+ docComponentsRendered,
+ dangerousAsPath,
+ canonicalBase,
+ ampPath,
+ inAmpMode,
+ isDevelopment: !!dev,
+ hybridAmp,
+ dynamicImports,
+ assetPrefix,
+ headTags,
+ unstable_runtimeJS,
+ unstable_JsPreload,
+ devOnlyCacheBusterQueryString,
+ scriptLoader,
+ locale,
+ disableOptimizedLoading,
+ ...docProps,
+ })}
+ </AmpStateContext.Provider>
+ )
+ );
+}
+
+class ServerRouter implements NextRouter {
+ route: string;
+ pathname: string;
+ query: ParsedUrlQuery;
+ asPath: string;
+ basePath: string;
+ events: any;
+ isFallback: boolean;
+ locale?: string;
+ isReady: boolean;
+ locales?: string[];
+ defaultLocale?: string;
+ domainLocales?: DomainLocale[];
+ isPreview: boolean;
+ isLocaleDomain: boolean;
+
+ constructor(
+ pathname: string,
+ query: ParsedUrlQuery,
+ as: string,
+ { isFallback }: { isFallback: boolean },
+ isReady: boolean,
+ basePath: string,
+ locale?: string,
+ locales?: string[],
+ defaultLocale?: string,
+ domainLocales?: DomainLocale[],
+ isPreview?: boolean,
+ isLocaleDomain?: boolean
+ ) {
+ this.route = pathname.replace(/\/$/, "") || "/";
+ this.pathname = pathname;
+ this.query = query;
+ this.asPath = as;
+ this.isFallback = isFallback;
+ this.basePath = basePath;
+ this.locale = locale;
+ this.locales = locales;
+ this.defaultLocale = defaultLocale;
+ this.isReady = isReady;
+ this.domainLocales = domainLocales;
+ this.isPreview = !!isPreview;
+ this.isLocaleDomain = !!isLocaleDomain;
+ }
+
+ push(): any {
+ noRouter();
+ }
+ replace(): any {
+ noRouter();
+ }
+ reload() {
+ noRouter();
+ }
+ back() {
+ noRouter();
+ }
+ prefetch(): any {
+ noRouter();
+ }
+ beforePopState() {
+ noRouter();
+ }
+}
+
+function noRouter() {
+ const message =
+ 'No router instance found. you should only use "next/router" inside the client side of your app. https://nextjs.org/docs/messages/no-router-instance';
+ throw new Error(message);
+}
+
+function enhanceComponents(
+ options: ComponentsEnhancer,
+ App: AppType,
+ Component: NextComponentType
+): {
+ App: AppType;
+ Component: NextComponentType;
+} {
+ // For backwards compatibility
+ if (typeof options === "function") {
+ return {
+ App,
+ Component: options(Component),
+ };
+ }
+
+ return {
+ App: options.enhanceApp ? options.enhanceApp(App) : App,
+ Component: options.enhanceComponent
+ ? options.enhanceComponent(Component)
+ : Component,
+ };
+}
+
+Object.defineProperty(NextDocument.Head.prototype, "getScripts", {
+ get() {
+ return getScripts;
+ },
+});
+Object.defineProperty(NextDocument.NextScript.prototype, "getScripts", {
+ get() {
+ return getScripts;
+ },
+});
+
+export async function render({
+ route,
+ PageNamespace,
+ AppNamespace,
+ appStylesheets = [],
+ pageStylesheets = [],
+ DocumentNamespace = null,
+ buildId,
+}: {
+ buildId: number;
+ route: any;
+ PageNamespace: { default: NextComponentType<any> };
+ AppNamespace: { default: NextComponentType<any> } | null;
+ DocumentNamespace: Object | null;
+ appStylesheets: string[];
+ pageStylesheets: string[];
+}): Promise<Response> {
+ const { default: Component, getStaticProps = null } = PageNamespace || {};
+ const { default: AppComponent_ } = AppNamespace || {};
+ var query = Object.assign({}, route.query);
+
+ // These are reversed in our Router versus Next.js...mostly due to personal preference.
+ const pathname = "/" + route.name;
+ var asPath = route.pathname;
+
+ const AppComponent = AppComponent_ || App.default;
+ const Document =
+ (DocumentNamespace && DocumentNamespace.default) || NextDocument.default;
+ // Document.Html.prototype.getScripts = getScripts;
+ // }
+
+ const callMiddleware = async (method: string, args: any[], props = false) => {
+ let results: any = props ? {} : [];
+
+ if ((Document as any)[`${method}Middleware`]) {
+ let middlewareFunc = await (Document as any)[`${method}Middleware`];
+ middlewareFunc = middlewareFunc.default || middlewareFunc;
+
+ const curResults = await middlewareFunc(...args);
+ if (props) {
+ for (const result of curResults) {
+ results = {
+ ...results,
+ ...result,
+ };
+ }
+ } else {
+ results = curResults;
+ }
+ }
+ return results;
+ };
+
+ const headTags = (...args: any) => callMiddleware("headTags", args);
+
+ const isFallback = !!query.__nextFallback;
+ delete query.__nextFallback;
+ delete query.__nextLocale;
+ delete query.__nextDefaultLocale;
+
+ const isSSG = !!getStaticProps;
+ const isBuildTimeSSG = isSSG && false;
+ const defaultAppGetInitialProps =
+ App.getInitialProps === (App as any).origGetInitialProps;
+
+ const hasPageGetInitialProps = !!(Component as any).getInitialProps;
+ const pageIsDynamic = route.kind === "dynamic";
+ const isAutoExport = false;
+
+ if (isAutoExport || isFallback) {
+ // remove query values except ones that will be set during export
+ query = {
+ ...(query.amp
+ ? {
+ amp: query.amp,
+ }
+ : {}),
+ };
+ asPath = `${asPath}${
+ // ensure trailing slash is present for non-dynamic auto-export pages
+ asPath.endsWith("/") && asPath !== "/" && !pageIsDynamic ? "/" : ""
+ }`;
+ }
+
+ let head: JSX.Element[] = [
+ <meta charSet="utf-8" />,
+ <meta name="viewport" content="width=device-width" />,
+ ];
+
+ const nextExport = isAutoExport || isFallback;
+ const reactLoadableModules: string[] = [];
+ var scriptLoader = {};
+ const AppContainer = ({ children }: any) => (
+ <RouterContext.Provider value={router}>
+ {/* <AmpStateContext.Provider value={ampState}> */}
+ <HeadManagerContext.Provider
+ value={{
+ updateHead: (state) => {
+ head = state;
+ },
+ updateScripts: (scripts) => {
+ scriptLoader = scripts;
+ },
+ scripts: {},
+ mountedInstances: new Set(),
+ }}
+ >
+ <LoadableContext.Provider
+ value={(moduleName) => reactLoadableModules.push(moduleName)}
+ >
+ {children}
+ </LoadableContext.Provider>
+ </HeadManagerContext.Provider>
+ {/* </AmpStateContext.Provider> */}
+ </RouterContext.Provider>
+ );
+
+ await Loadable.preloadAll(); // Make sure all dynamic imports are loaded
+
+ const router = new ServerRouter(
+ pathname,
+ query,
+ asPath,
+ {
+ isFallback: isFallback,
+ },
+ true,
+ Wundle.origin,
+ null,
+ [], // renderOpts.locales,
+ null, //renderOpts.defaultLocale,
+ [], // renderOpts.domainLocales,
+ false,
+ false
+ );
+
+ const ctx = {
+ err: null,
+ req: undefined,
+ res: undefined,
+ pathname,
+ query,
+ asPath,
+ locale: null,
+ locales: [],
+ defaultLocale: null,
+ AppTree: (props: any) => {
+ return (
+ <AppContainer>
+ <App {...props} Component={Component} router={router} />
+ </AppContainer>
+ );
+ },
+ };
+
+ var props = await loadGetInitialProps(AppComponent, {
+ AppTree: ctx.AppTree,
+ Component,
+ router,
+ ctx,
+ });
+
+ const renderToString = ReactDOMServer.renderToString;
+ const ErrorDebug = null;
+
+ const renderPage: RenderPage = (
+ options: ComponentsEnhancer = {}
+ ): RenderPageResult | Promise<RenderPageResult> => {
+ if (ctx.err && ErrorDebug) {
+ const htmlOrPromise = renderToString(<ErrorDebug error={ctx.err} />);
+ return typeof htmlOrPromise === "string"
+ ? { html: htmlOrPromise, head }
+ : htmlOrPromise.then((html) => ({
+ html,
+ head,
+ }));
+ }
+
+ if (dev && (props.router || props.Component)) {
+ throw new Error(
+ `'router' and 'Component' can not be returned in getInitialProps from _app.js https://nextjs.org/docs/messages/cant-override-next-props`
+ );
+ }
+
+ const { App: EnhancedApp, Component: EnhancedComponent } =
+ enhanceComponents(options, AppComponent, Component);
+
+ const htmlOrPromise = renderToString(
+ <AppContainer>
+ <EnhancedApp Component={EnhancedComponent} router={router} {...props} />
+ </AppContainer>
+ );
+ return typeof htmlOrPromise === "string"
+ ? { html: htmlOrPromise, head }
+ : htmlOrPromise.then((html) => ({
+ html,
+ head,
+ }));
+ };
+
+ const documentCtx = { ...ctx, renderPage };
+ const docProps: DocumentInitialProps = await loadGetInitialProps(
+ Document,
+ documentCtx
+ );
+
+ if (!docProps || typeof docProps.html !== "string") {
+ const message = `"${getDisplayName(
+ Document
+ )}.getInitialProps()" should resolve to an object with a "html" prop set with a valid html string`;
+ throw new Error(message);
+ }
+
+ const renderOpts = {
+ params: route.params,
+ };
+ // renderOpts.params = _params || params;
+
+ // parsedUrl.pathname = denormalizePagePath(parsedUrl.pathname!);
+ // renderOpts.resolvedUrl = formatUrl({
+ // ...parsedUrl,
+ // query: origQuery,
+ // });
+ const docComponentsRendered: DocumentProps["docComponentsRendered"] = {};
+ const isPreview = false;
+ const getServerSideProps = PageNamespace.getServerSideProps;
+ let html = renderDocument(Document, {
+ docComponentsRendered,
+ ...renderOpts,
+ disableOptimizedLoading: false,
+ canonicalBase: Wundle.origin,
+ buildManifest: {
+ devFiles: [],
+ allFiles: [],
+ polyfillFiles: [],
+ lowPriorityFiles: [],
+ pages: {
+ "/_app": [Wundle.routesDir + "_app", ...appStylesheets],
+ [pathname]: [...pageStylesheets, route.scriptSrc],
+ },
+ },
+ // Only enabled in production as development mode has features relying on HMR (style injection for example)
+ unstable_runtimeJS: true,
+ // process.env.NODE_ENV === "production"
+ // ? pageConfig.unstable_runtimeJS
+ // : undefined,
+ // unstable_JsPreload: pageConfig.unstable_JsPreload,
+ unstable_JsPreload: true,
+ dangerousAsPath: router.asPath,
+ ampState: undefined,
+ props,
+ assetPrefix: "",
+ headTags: await headTags(documentCtx),
+ isFallback,
+ docProps,
+ pathname,
+ ampPath: undefined,
+ query,
+ inAmpMode: false,
+ hybridAmp: undefined,
+ dynamicImportsIds: [], // Array.from(dynamicImportsIds),
+ dynamicImports: [], //Array.from(dynamicImports),
+ gsp: !!getStaticProps ? true : undefined,
+ gssp: !!getServerSideProps ? true : undefined,
+ gip: hasPageGetInitialProps ? true : undefined,
+ appGip: !defaultAppGetInitialProps ? true : undefined,
+ devOnlyCacheBusterQueryString: "",
+ scriptLoader,
+ isPreview: isPreview === true ? true : undefined,
+ autoExport: isAutoExport === true ? true : undefined,
+ nextExport: nextExport === true ? true : undefined,
+ }).replaceAll("/_next//_next", "/_next");
+ return new Response(html);
+}
diff --git a/demos/css-stress-test/nexty/server.development.tsx b/demos/css-stress-test/nexty/server.development.tsx
index ab330359e..78017f885 100644
--- a/demos/css-stress-test/nexty/server.development.tsx
+++ b/demos/css-stress-test/nexty/server.development.tsx
@@ -1,70 +1,67 @@
-import ReactDOMServer from "react-dom/server.browser";
+import { render } from "./renderDocument";
-addEventListener(
- "fetch",
+let buildId = 0;
- // Anything imported in here will automatically reload in development.
- // The module registry cache is reset at the end of each page load
- async (event: FetchEvent) => {
- var appRoute;
+var DocumentNamespacePromise;
+DocumentNamespacePromise = import(Wundle.routesDir + "_document");
+var DocumentLoaded = false;
+var DocumentNamespace;
+
+addEventListener("fetch", async (event: FetchEvent) => {
+ if (!DocumentLoaded) {
+ DocumentLoaded = true;
try {
- appRoute = await import(Wundle.routesDir + "_app");
+ DocumentNamespace = await DocumentNamespacePromise;
} catch (exception) {
- appRoute = null;
+ DocumentNamespace = null;
}
+ }
- var route = Wundle.match(event);
-
- // This imports the currently matched route.
- const { default: PageComponent } = await import(route.filePath);
-
- // This returns all .css files that were imported in the line above.
- // It's recursive, so any file that imports a CSS file will be included.
- const stylesheets = Wundle.getImportedStyles() as string[];
-
- // Ordinarily, this is just the formatted filepath URL (rewritten to match the public url of the HTTP server)
- // But, when you set `client` in the package.json for the framework, this becomes a path like this:
- // "/pages/index.js" -> "pages/index.entry.js" ("entry" is for entry point)
- const src = route.scriptSrc;
-
- // From there, the inside of that script like this:
- // ```
- // import * as Framework from 'framework-path';
- // import * as EntryPoint from 'entry-point';
- //
- // Framework.default(EntryPoint);
- // ```
- // That's how the client-side framework loads
+ var appRoute;
- const response = new Response(`
- <!DOCTYPE html>
-<html>
- <head>
- ${stylesheets
- .map((style) => `<link rel="stylesheet" href="${style}">`)
- .join("\n")}
+ try {
+ appRoute = await import(Wundle.routesDir + "_app");
+ } catch (exception) {
+ appRoute = null;
+ }
+ const appStylesheets = Wundle.getImportedStyles() as string[];
- <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>
+ var route = Wundle.match(event);
- <div id="#__next">${ReactDOMServer.renderToString(<PageComponent />)}</div>
+ // This imports the currently matched route.
+ const PageNamespace = await import(route.filePath);
- <script src="${src}" async type="module"></script>
- </body>
-</html>
- `);
+ // This returns all .css files that were imported in the line above.
+ // It's recursive, so any file that imports a CSS file will be included.
+ const pageStylesheets = Wundle.getImportedStyles() as string[];
- event.respondWith(response);
- }
-);
+ event.respondWith(
+ await render({
+ route,
+ PageNamespace,
+ appStylesheets,
+ pageStylesheets,
+ DocumentNamespace,
+ AppNamespace: appRoute,
+ buildId,
+ })
+ );
+ buildId++;
+});
// typescript isolated modules
export {};
declare var Wundle: any;
+
+function getNextData(request: Request, route) {
+ return {
+ NEXT_DATA: {
+ query: route.query,
+ props: {},
+ page: route.path,
+ buildId: buildId.toString(16),
+ },
+ };
+}
diff --git a/demos/css-stress-test/nexty/tsconfig.json b/demos/css-stress-test/nexty/tsconfig.json
new file mode 100644
index 000000000..679268d71
--- /dev/null
+++ b/demos/css-stress-test/nexty/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "lib": ["dom", "dom.iterable", "esnext", "WebWorker"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "baseUrl": ".",
+ "paths": {
+ "path": ["node_modules/path-browserify"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
diff --git a/src/bundler.zig b/src/bundler.zig
index 49b9f496b..377079355 100644
--- a/src/bundler.zig
+++ b/src/bundler.zig
@@ -101,13 +101,13 @@ pub const ClientEntryPoint = struct {
\\ globalThis.onerror.loaded = loaded;
\\}}
\\
- \\import * as boot from '{s}';
+ \\import boot from '{s}';
\\loaded.boot = true;
\\if ('setLoaded' in boot) boot.setLoaded(loaded);
\\import * as EntryPoint from '{s}{s}';
\\loaded.entry = true;
\\
- \\if (!('default' in boot) ) {{
+ \\if (!boot) {{
\\ const now = Date.now();
\\ debugger;
\\ const elapsed = Date.now() - now;
@@ -116,7 +116,7 @@ pub const ClientEntryPoint = struct {
\\ }}
\\}}
\\
- \\boot.default(EntryPoint, loaded);
+ \\boot(EntryPoint, loaded);
,
.{
client,
@@ -2126,15 +2126,15 @@ pub const Transformer = struct {
) !options.TransformResult {
js_ast.Expr.Data.Store.create(allocator);
js_ast.Stmt.Data.Store.create(allocator);
+ const platform = options.Platform.from(opts.platform);
- var define = try options.definesFromTransformOptions(allocator, log, opts.define, false);
+ var define = try options.definesFromTransformOptions(allocator, log, opts.define, false, platform);
const cwd = if (opts.absolute_working_dir) |workdir| try std.fs.realpathAlloc(allocator, workdir) else try std.process.getCwdAlloc(allocator);
const output_dir_parts = [_]string{ try std.process.getCwdAlloc(allocator), opts.output_dir orelse "out" };
const output_dir = try std.fs.path.join(allocator, &output_dir_parts);
var output_files = try std.ArrayList(options.OutputFile).initCapacity(allocator, opts.entry_points.len);
- const platform = options.Platform.from(opts.platform);
const out_extensions = platform.outExtensions(allocator);
var loader_map = try options.loadersFromTransformOptions(allocator, opts.loaders);
diff --git a/src/css_scanner.zig b/src/css_scanner.zig
index e39515292..298c5f81a 100644
--- a/src/css_scanner.zig
+++ b/src/css_scanner.zig
@@ -898,16 +898,21 @@ pub fn NewWriter(
switch (chunk.content) {
.t_url => |url| {},
.t_import => |import| {
- try writer.buildCtx.addCSSImport(
- try writer.linker.resolveCSS(
- writer.source.path,
- import.text.utf8,
- chunk.range,
- import_record.ImportKind.at,
- Options.BundleOptions.ImportPathFormat.absolute_path,
- true,
- ),
+ const resolved = try writer.linker.resolveCSS(
+ writer.source.path,
+ import.text.utf8,
+ chunk.range,
+ import_record.ImportKind.at,
+ Options.BundleOptions.ImportPathFormat.absolute_path,
+ true,
);
+
+ // TODO: just check is_external instead
+ if (strings.startsWith(import.text.utf8, "https://") or strings.startsWith(import.text.utf8, "http://")) {
+ return;
+ }
+
+ try writer.buildCtx.addCSSImport(resolved);
},
.t_verbatim => |verbatim| {},
}
diff --git a/src/http.zig b/src/http.zig
index 06eaf7a18..ffb7a3d72 100644
--- a/src/http.zig
+++ b/src/http.zig
@@ -1573,7 +1573,7 @@ pub const RequestContext = struct {
break :brk try ctx.bundler.buildFile(
&ctx.log,
ctx.allocator,
- ctx.url.path,
+ ctx.url.pathWithoutOrigin(ctx.bundler.options.origin),
ctx.url.extname,
true,
);
@@ -1581,7 +1581,7 @@ pub const RequestContext = struct {
break :brk try ctx.bundler.buildFile(
&ctx.log,
ctx.allocator,
- ctx.url.path,
+ ctx.url.pathWithoutOrigin(ctx.bundler.options.origin),
ctx.url.extname,
false,
);
diff --git a/src/http/url_path.zig b/src/http/url_path.zig
index a2e55555d..e82d5601a 100644
--- a/src/http/url_path.zig
+++ b/src/http/url_path.zig
@@ -1,6 +1,8 @@
usingnamespace @import("../global.zig");
-const std = @import("std");
+const PercentEncoding = @import("../query_string_map.zig").PercentEncoding;
+const std = @import("std");
+const allocators = @import("../allocators.zig");
const URLPath = @This();
extname: string = "",
@@ -8,17 +10,63 @@ path: string = "",
pathname: string = "",
first_segment: string = "",
query_string: string = "",
+const toMutable = allocators.constStrToU8;
+
+// TODO: use a real URL parser
+// this treats a URL like /_next/ identically to /
+pub fn pathWithoutOrigin(this: *const URLPath, _origin: string) string {
+ if (_origin.len == 0) return this.path;
+ const leading_slash_offset: usize = if (_origin[0] == '/') 1 else 0;
+ const base = this.path;
+ const origin = _origin[leading_slash_offset..];
+
+ return if (base.len >= origin.len and strings.eql(base[0..origin.len], origin)) base[origin.len..] else base;
+}
+
+// optimization: very few long strings will be URL-encoded
+// we're allocating virtual memory here, so if we never use it, it won't be allocated
+// and even when they're, they're probably rarely going to be > 1024 chars long
+// so we can have a big and little one and almost always use the little one
+threadlocal var temp_path_buf: [1024]u8 = undefined;
+threadlocal var big_temp_path_buf: [16384]u8 = undefined;
+
+pub fn parse(possibly_encoded_pathname_: string) URLPath {
+ var decoded_pathname = possibly_encoded_pathname_;
+
+ if (strings.indexOfChar(decoded_pathname, '%') != null) {
+ var possibly_encoded_pathname = switch (decoded_pathname.len) {
+ 0...1024 => &temp_path_buf,
+ else => &big_temp_path_buf,
+ };
+ possibly_encoded_pathname = possibly_encoded_pathname[0..std.math.min(
+ possibly_encoded_pathname_.len,
+ possibly_encoded_pathname.len,
+ )];
+
+ std.mem.copy(u8, possibly_encoded_pathname, possibly_encoded_pathname_[0..possibly_encoded_pathname.len]);
+ var clone = possibly_encoded_pathname[0..possibly_encoded_pathname.len];
+
+ var fbs = std.io.fixedBufferStream(
+ // This is safe because:
+ // - this comes from a non-const buffer
+ // - percent *decoding* will always be <= length of the original string (no buffer overflow)
+ toMutable(
+ possibly_encoded_pathname,
+ ),
+ );
+ var writer = fbs.writer();
+ decoded_pathname = possibly_encoded_pathname[0 .. PercentEncoding.decode(@TypeOf(writer), writer, clone) catch unreachable];
+ }
-// This does one pass over the URL path instead of like 4
-pub fn parse(raw_path: string) URLPath {
var question_mark_i: i16 = -1;
var period_i: i16 = -1;
var first_segment_end: i16 = std.math.maxInt(i16);
var last_slash: i16 = -1;
- var i: i16 = @intCast(i16, raw_path.len) - 1;
+ var i: i16 = @intCast(i16, decoded_pathname.len) - 1;
+
while (i >= 0) : (i -= 1) {
- const c = raw_path[@intCast(usize, i)];
+ const c = decoded_pathname[@intCast(usize, i)];
switch (c) {
'?' => {
@@ -52,24 +100,24 @@ pub fn parse(raw_path: string) URLPath {
const extname = brk: {
if (question_mark_i > -1 and period_i > -1) {
period_i += 1;
- break :brk raw_path[@intCast(usize, period_i)..@intCast(usize, question_mark_i)];
+ break :brk decoded_pathname[@intCast(usize, period_i)..@intCast(usize, question_mark_i)];
} else if (period_i > -1) {
period_i += 1;
- break :brk raw_path[@intCast(usize, period_i)..];
+ break :brk decoded_pathname[@intCast(usize, period_i)..];
} else {
break :brk &([_]u8{});
}
};
- const path = if (question_mark_i < 0) raw_path[1..] else raw_path[1..@intCast(usize, question_mark_i)];
+ const path = if (question_mark_i < 0) decoded_pathname[1..] else decoded_pathname[1..@intCast(usize, question_mark_i)];
- const first_segment = raw_path[1..std.math.min(@intCast(usize, first_segment_end), raw_path.len)];
+ const first_segment = decoded_pathname[1..std.math.min(@intCast(usize, first_segment_end), decoded_pathname.len)];
return URLPath{
.extname = extname,
- .pathname = raw_path,
+ .pathname = decoded_pathname,
.first_segment = first_segment,
- .path = if (raw_path.len == 1) "." else path,
- .query_string = if (question_mark_i > -1) raw_path[@intCast(usize, question_mark_i)..@intCast(usize, raw_path.len)] else "",
+ .path = if (decoded_pathname.len == 1) "." else path,
+ .query_string = if (question_mark_i > -1) decoded_pathname[@intCast(usize, question_mark_i)..@intCast(usize, decoded_pathname.len)] else "",
};
}
diff --git a/src/javascript/jsc/api/router.zig b/src/javascript/jsc/api/router.zig
index e2283e9c5..84562fee3 100644
--- a/src/javascript/jsc/api/router.zig
+++ b/src/javascript/jsc/api/router.zig
@@ -16,6 +16,7 @@ const Fs = @import("../../../fs.zig");
route: *const FilesystemRouter.Match,
query_string_map: ?QueryStringMap = null,
+param_map: ?QueryStringMap = null,
script_src: ?string = null,
script_src_buf: [1024]u8 = undefined,
@@ -223,6 +224,23 @@ pub const Instance = NewClass(
.ts = d.ts{
.@"return" = "Record<string, string | string[]>",
.tsdoc =
+ \\Route parameters & parsed query string values as a key-value object
+ \\
+ \\@example
+ \\```js
+ \\console.assert(router.query.id === "123");
+ \\console.assert(router.pathname === "/blog/posts/123");
+ \\console.assert(router.route === "blog/posts/[id]");
+ \\```
+ ,
+ },
+ },
+ .params = .{
+ .@"get" = getParams,
+ .ro = true,
+ .ts = d.ts{
+ .@"return" = "Record<string, string | string[]>",
+ .tsdoc =
\\Route parameters as a key-value object
\\
\\@example
@@ -372,6 +390,34 @@ pub fn getScriptSrc(
return js.JSValueMakeString(ctx, ZigString.init(src).toJSStringRef());
}
+pub fn getParams(
+ this: *Router,
+ ctx: js.JSContextRef,
+ thisObject: js.JSObjectRef,
+ prop: js.JSStringRef,
+ exception: js.ExceptionRef,
+) js.JSValueRef {
+ if (this.param_map == null) {
+ if (this.route.params.len > 0) {
+ if (QueryStringMap.initWithScanner(getAllocator(ctx), CombinedScanner.init(
+ "",
+ this.route.pathnameWithoutLeadingSlash(),
+ this.route.name,
+ this.route.params,
+ ))) |map| {
+ this.param_map = map;
+ } else |err| {}
+ }
+ }
+
+ // If it's still null, there are no params
+ if (this.param_map) |*map| {
+ return createQueryObject(ctx, map, exception);
+ } else {
+ return JSValue.createEmptyObject(VirtualMachine.vm.global, 0).asRef();
+ }
+}
+
pub fn getQuery(
this: *Router,
ctx: js.JSContextRef,
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index 4902c731a..e17d4c7ab 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -205,6 +205,8 @@ pub const Properties = struct {
pub const default: string = "default";
pub const include: string = "include";
+ pub const env: string = "env";
+
pub const GET = "GET";
pub const PUT = "PUT";
pub const POST = "POST";
@@ -278,6 +280,8 @@ pub const Properties = struct {
pub var navigate: js.JSStringRef = undefined;
pub var follow: js.JSStringRef = undefined;
+
+ pub const env: js.JSStringRef = undefined;
};
pub fn init() void {
diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp
index fbe3c22ae..45a68954e 100644
--- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp
+++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp
@@ -121,9 +121,40 @@ void GlobalObject::setConsole(void *console) {
this->setConsoleClient(makeWeakPtr(m_console));
}
+// This is not a publicly exposed API currently.
+// This is used by the bundler to make Response, Request, FetchEvent,
+// and any other objects available globally.
void GlobalObject::installAPIGlobals(JSClassRef *globals, int count) {
WTF::Vector<GlobalPropertyInfo> extraStaticGlobals;
- extraStaticGlobals.reserveCapacity((size_t)count);
+ extraStaticGlobals.reserveCapacity((size_t)count + 1);
+
+ // This is not nearly a complete implementation. It's just enough to make some npm packages that
+ // were compiled with Webpack to run without crashing in this environment.
+ JSC::JSObject *process = JSC::constructEmptyObject(this, this->objectPrototype(), 4);
+
+ // The transpiler inlines all defined process.env vars & dead code eliminates as relevant
+ // so this is just to return undefined for any missing ones and not crash if something tries to
+ // modify it or it wasn't statically analyzable
+ JSC::JSObject *processDotEnv = JSC::constructEmptyObject(this, this->objectPrototype(), 0);
+
+ process->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "env"), processDotEnv);
+
+ // this should be transpiled out, but just incase
+ process->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "browser"),
+ JSC::JSValue(false));
+
+ // this gives some way of identifying at runtime whether the SSR is happening in node or not.
+ // this should probably be renamed to what the name of the bundler is, instead of "notNodeJS"
+ // but it must be something that won't evaluate to truthy in Node.js
+ process->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "notNodeJS"),
+ JSC::JSValue(true));
+#if defined(__APPLE__)
+ process->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "platform"),
+ JSC::jsString(this->vm(), WTF::String("darwin")));
+#else
+ process->putDirect(this->vm(), JSC::Identifier::fromString(this->vm(), "platform"),
+ JSC::jsString(this->vm(), WTF::String("linux")));
+#endif
for (int i = 0; i < count; i++) {
auto jsClass = globals[i];
@@ -137,7 +168,12 @@ void GlobalObject::installAPIGlobals(JSClassRef *globals, int count) {
GlobalPropertyInfo{JSC::Identifier::fromString(vm(), jsClass->className()),
JSC::JSValue(object), JSC::PropertyAttribute::DontDelete | 0});
}
- this->addStaticGlobals(extraStaticGlobals.data(), count);
+
+ extraStaticGlobals.uncheckedAppend(
+ GlobalPropertyInfo{JSC::Identifier::fromString(vm(), "process"), JSC::JSValue(process),
+ JSC::PropertyAttribute::DontDelete | 0});
+
+ this->addStaticGlobals(extraStaticGlobals.data(), extraStaticGlobals.size());
extraStaticGlobals.releaseBuffer();
}
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index a2d9220ad..0b96ca3ed 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -117,7 +117,7 @@ pub const Wundle = struct {
prop: js.JSStringRef,
exception: js.ExceptionRef,
) js.JSValueRef {
- if (!VirtualMachine.vm.bundler.options.routes.routes_enabled or VirtualMachine.vm.bundler.options.routes.dir.len > 0) {
+ if (!VirtualMachine.vm.bundler.options.routes.routes_enabled or VirtualMachine.vm.bundler.options.routes.dir.len == 0) {
return js.JSValueMakeUndefined(ctx);
}
@@ -238,6 +238,7 @@ pub const VirtualMachine = struct {
log: *logger.Log,
event_listeners: EventListenerMixin.Map,
main: string = "",
+ process: js.JSObjectRef = null,
pub var vm_loaded = false;
pub var vm: *VirtualMachine = undefined;
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index 8b9649aa2..f6b59141c 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -70,7 +70,7 @@ pub const ImportScanner = struct {
var stmt: Stmt = _stmt;
switch (stmt.data) {
.s_import => |st| {
- var record: ImportRecord = p.import_records.items[st.import_record_index];
+ var record: *ImportRecord = &p.import_records.items[st.import_record_index];
// The official TypeScript compiler always removes unused imported
// symbols. However, we deliberately deviate from the official
@@ -285,7 +285,7 @@ pub const ImportScanner = struct {
// it's really stupid to import all 1,000 components from that design system
// when you just want <Button />
const namespace_ref = st.namespace_ref;
- const convert_star_to_clause = p.symbols.items[namespace_ref.inner_index].use_count_estimate == 0;
+ const convert_star_to_clause = p.symbols.items[namespace_ref.inner_index].use_count_estimate == 0 and st.default_name == null;
if (convert_star_to_clause and !keep_unused_imports) {
st.star_name_loc = null;
@@ -1879,7 +1879,7 @@ pub const Parser = struct {
var decls = try p.allocator.alloc(G.Decl, decls_count);
var jsx_part_stmts = try p.allocator.alloc(Stmt, stmts_count);
// Use the same array for storing the require call target of potentially both JSX runtimes
- var require_call_args_base = p.allocator.alloc(Expr, imports_count) catch unreachable;
+ var require_call_args_base = p.allocator.alloc(Expr, if (p.options.can_import_from_bundle) 0 else imports_count) catch unreachable;
var import_records = try p.allocator.alloc(u32, imports_count);
var decl_i: usize = 0;
@@ -1889,16 +1889,21 @@ pub const Parser = struct {
var stmt_i: usize = 0;
if (jsx_symbol.use_count_estimate > 0) {
- require_call_args_base[require_call_args_i] = p.e(E.Identifier{ .ref = automatic_namespace_ref }, loc);
- require_call_args_i += 1;
-
- var require_call_args = require_call_args_base[0..require_call_args_i];
-
declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_runtime_ref, .is_top_level = true };
declared_symbols_i += 1;
declared_symbols[declared_symbols_i] = .{ .ref = automatic_namespace_ref, .is_top_level = true };
declared_symbols_i += 1;
- var require_call = p.callRequireOrBundledRequire(require_call_args);
+
+ const automatic_identifier = p.e(E.Identifier{ .ref = automatic_namespace_ref }, loc);
+ const dot_call_target = brk: {
+ if (p.options.can_import_from_bundle) {
+ break :brk automatic_identifier;
+ } else {
+ require_call_args_base[require_call_args_i] = automatic_identifier;
+ require_call_args_i += 1;
+ break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]);
+ }
+ };
decls[decl_i] = G.Decl{
.binding = p.b(
@@ -1909,7 +1914,7 @@ pub const Parser = struct {
),
.value = p.e(
E.Dot{
- .target = require_call,
+ .target = dot_call_target,
.name = p.options.jsx.jsx,
.name_loc = loc,
.can_be_removed_if_unused = true,
@@ -1938,6 +1943,7 @@ pub const Parser = struct {
// When everything is CommonJS
// We import JSX like this:
// var {jsxDev} = require("react/jsx-dev")
+
jsx_part_stmts[stmt_i] = p.s(S.Import{
.namespace_ref = automatic_namespace_ref,
.star_name_loc = loc,
@@ -1962,9 +1968,20 @@ pub const Parser = struct {
}
if (jsx_classic_symbol.use_count_estimate > 0) {
- require_call_args_base[require_call_args_i] = p.e(E.Identifier{ .ref = classic_namespace_ref }, loc);
- var require_call_args = require_call_args_base[require_call_args_i..];
- var require_call = p.callRequireOrBundledRequire(require_call_args);
+ const classic_identifier = p.e(E.Identifier{ .ref = classic_namespace_ref }, loc);
+
+ const dot_call_target = brk: {
+ // var react = $aopaSD123();
+
+ if (p.options.can_import_from_bundle) {
+ break :brk classic_identifier;
+ } else {
+ require_call_args_base[require_call_args_i] = classic_identifier;
+ require_call_args_i += 1;
+ break :brk p.callUnbundledRequire(require_call_args_base[0..require_call_args_i]);
+ }
+ };
+
if (jsx_factory_symbol.use_count_estimate > 0) {
declared_symbols[declared_symbols_i] = .{ .ref = p.jsx_factory_ref, .is_top_level = true };
declared_symbols_i += 1;
@@ -1977,7 +1994,7 @@ pub const Parser = struct {
),
.value = p.e(
E.Dot{
- .target = require_call,
+ .target = dot_call_target,
.name = p.options.jsx.factory[p.options.jsx.factory.len - 1],
.name_loc = loc,
.can_be_removed_if_unused = true,
@@ -2000,7 +2017,7 @@ pub const Parser = struct {
),
.value = p.e(
E.Dot{
- .target = require_call,
+ .target = dot_call_target,
.name = p.options.jsx.fragment[p.options.jsx.fragment.len - 1],
.name_loc = loc,
.can_be_removed_if_unused = true,
@@ -2676,7 +2693,7 @@ pub fn NewParser(
cjs_import_name,
base_identifier_name,
);
- std.mem.copy(u8, cjs_import_name[base_identifier_name.len..], suffix);
+ std.mem.copy(u8, cjs_import_name[base_identifier_name.len - 1 ..], suffix);
const namespace_ref = p.declareSymbol(.hoisted, arg.loc, cjs_import_name) catch unreachable;
@@ -2895,12 +2912,8 @@ pub fn NewParser(
// If we're auto-importing JSX and it's bundled, we use the bundled version
// This means we need to transform from require(react) to react()
// unless we're building inside of speedy, then it's just normal commonjs
- pub fn callRequireOrBundledRequire(p: *P, require_args: []Expr) Expr {
- if (p.options.can_import_from_bundle) {
- return p.e(E.Call{ .target = require_args[0] }, require_args[0].loc);
- } else {
- return p.callRuntime(require_args[0].loc, "__require", require_args);
- }
+ pub fn callUnbundledRequire(p: *P, require_args: []Expr) Expr {
+ return p.callRuntime(require_args[0].loc, "__require", require_args);
}
pub fn recordExport(p: *P, loc: logger.Loc, alias: string, ref: Ref) !void {
diff --git a/src/js_printer.zig b/src/js_printer.zig
index 1f43479a6..8a5c81c8c 100644
--- a/src/js_printer.zig
+++ b/src/js_printer.zig
@@ -2835,9 +2835,15 @@ pub fn NewPrinter(
}
} else if (record.is_bundled) {
p.print("import {");
+
p.printLoadFromBundleWithoutCall(s.import_record_index);
- p.print(" as ");
- p.printSymbol(s.namespace_ref);
+
+ if (!record.contains_import_star) {
+ p.print(" as ");
+
+ p.printSymbol(s.namespace_ref);
+ }
+
p.print("} from ");
p.printQuotedUTF8(record.path.text, false);
p.printSemicolonAfterStatement();
@@ -2846,6 +2852,7 @@ pub fn NewPrinter(
p.printIndent();
p.printSpaceBeforeIdentifier();
p.print("var {");
+
for (s.items) |item, i| {
p.print(item.alias);
const name = p.renamer.nameForSymbol(item.name.ref.?);
@@ -2859,22 +2866,43 @@ pub fn NewPrinter(
}
}
p.print("} = ");
- p.printSymbol(s.namespace_ref);
+
+ if (!record.contains_import_star) {
+ p.printSymbol(s.namespace_ref);
+ } else {
+ p.printLoadFromBundleWithoutCall(s.import_record_index);
+ }
p.print("()");
p.printSemicolonAfterStatement();
} else if (s.default_name) |default_name| {
p.printIndent();
p.printSpaceBeforeIdentifier();
+
p.print("var {default: ");
+
p.printSymbol(default_name.ref.?);
p.print("} = ");
+
+ if (!record.contains_import_star) {
+ p.printSymbol(s.namespace_ref);
+ } else {
+ p.printLoadFromBundleWithoutCall(s.import_record_index);
+ }
+
+ p.print("()");
+ p.printSemicolonAfterStatement();
+ } else if (record.contains_import_star) {
+ p.print("var ");
p.printSymbol(s.namespace_ref);
+ p.print(" = ");
+ p.printLoadFromBundleWithoutCall(s.import_record_index);
p.print("()");
p.printSemicolonAfterStatement();
}
return;
}
+
p.print("import");
p.printSpace();
diff --git a/src/options.zig b/src/options.zig
index f93937b24..a4302a703 100644
--- a/src/options.zig
+++ b/src/options.zig
@@ -250,6 +250,17 @@ pub const Platform = enum {
speedy,
node,
+ const browser_define_value_true = "true";
+ const browser_define_value_false = "false";
+
+ pub fn processBrowserDefineValue(this: Platform) ?string {
+ return switch (this) {
+ .browser => browser_define_value_true,
+ .speedy, .node => browser_define_value_false,
+ else => null,
+ };
+ }
+
pub fn isWebLike(platform: Platform) bool {
return switch (platform) {
.neutral, .browser => true,
@@ -545,9 +556,20 @@ pub const DefaultUserDefines = struct {
pub const Key = "process.env.NODE_ENV";
pub const Value = "\"development\"";
};
+
+ pub const PlatformDefine = struct {
+ pub const Key = "process.browser";
+ pub const Value = []string{ "false", "true" };
+ };
};
-pub fn definesFromTransformOptions(allocator: *std.mem.Allocator, log: *logger.Log, _input_define: ?Api.StringMap, hmr: bool) !*defines.Define {
+pub fn definesFromTransformOptions(
+ allocator: *std.mem.Allocator,
+ log: *logger.Log,
+ _input_define: ?Api.StringMap,
+ hmr: bool,
+ platform: Platform,
+) !*defines.Define {
var input_user_define = _input_define orelse std.mem.zeroes(Api.StringMap);
var user_defines = try stringHashMapFromArrays(
@@ -564,6 +586,12 @@ pub fn definesFromTransformOptions(allocator: *std.mem.Allocator, log: *logger.L
try user_defines.put(DefaultUserDefines.HotModuleReloading.Key, DefaultUserDefines.HotModuleReloading.Value);
}
+ // Automatically set `process.browser` to `true` for browsers and false for node+js
+ // This enables some extra dead code elimination
+ if (platform.processBrowserDefineValue()) |value| {
+ _ = try user_defines.getOrPutValue(DefaultUserDefines.PlatformDefine.Key, value);
+ }
+
var resolved_defines = try defines.DefineData.from_input(user_defines, log, allocator);
return try defines.Define.init(
allocator,
@@ -672,7 +700,7 @@ pub const BundleOptions = struct {
var opts: BundleOptions = BundleOptions{
.log = log,
.resolve_mode = transform.resolve orelse .dev,
- .define = try definesFromTransformOptions(allocator, log, transform.define, transform.serve orelse false),
+ .define = undefined,
.loaders = try loadersFromTransformOptions(allocator, transform.loaders),
.output_dir = try fs.absAlloc(allocator, &output_dir_parts),
.platform = Platform.from(transform.platform),
@@ -712,6 +740,8 @@ pub const BundleOptions = struct {
else => {},
}
+ opts.define = try definesFromTransformOptions(allocator, log, transform.define, transform.serve orelse false, opts.platform);
+
if (!(transform.generate_node_module_bundle orelse false)) {
if (node_modules_bundle_existing) |node_mods| {
opts.node_modules_bundle = node_mods;
diff --git a/src/runtime.js b/src/runtime.js
index f065a1c4e..affd3bc4d 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -63,7 +63,7 @@ export var __commonJS = (cb, name) => {
get() {
return mod.exports;
},
- enumerable: false,
+ enumerable: true,
});
// If it's a namespace export without .default, pretend .default is the same as mod.exports
} else if (
@@ -74,7 +74,7 @@ export var __commonJS = (cb, name) => {
get() {
return mod.exports;
},
- enumerable: false,
+ enumerable: true,
});
}
diff --git a/src/runtime.version b/src/runtime.version
index 1a0bb1fa9..4cda1e7bd 100644
--- a/src/runtime.version
+++ b/src/runtime.version
@@ -1 +1 @@
-ca3ca9f44c907d4a \ No newline at end of file
+7d529e0a92952d7a \ No newline at end of file