From 9e3b2b96830ee6ec0ad0ab205fd630c6ec3419bc Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Wed, 11 May 2022 11:37:00 -0400 Subject: [PATCH] test(compiler): browser and server bundles for deno --- integration/deno-compiler-test.ts | 230 ++++++++++++++++++ integration/helpers/create-fixture.ts | 2 +- integration/helpers/deno-template/.gitignore | 5 + .../deno-template/app/entry.client.tsx | 5 + .../deno-template/app/entry.server.tsx | 22 ++ .../helpers/deno-template/app/root.tsx | 33 +++ .../helpers/deno-template/package.json | 17 ++ .../helpers/deno-template/public/favicon.ico | Bin 0 -> 16958 bytes .../helpers/deno-template/remix.config.js | 12 + integration/helpers/deno-template/server.ts | 14 ++ 10 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 integration/deno-compiler-test.ts create mode 100644 integration/helpers/deno-template/.gitignore create mode 100644 integration/helpers/deno-template/app/entry.client.tsx create mode 100644 integration/helpers/deno-template/app/entry.server.tsx create mode 100644 integration/helpers/deno-template/app/root.tsx create mode 100644 integration/helpers/deno-template/package.json create mode 100644 integration/helpers/deno-template/public/favicon.ico create mode 100644 integration/helpers/deno-template/remix.config.js create mode 100644 integration/helpers/deno-template/server.ts diff --git a/integration/deno-compiler-test.ts b/integration/deno-compiler-test.ts new file mode 100644 index 00000000000..80283133895 --- /dev/null +++ b/integration/deno-compiler-test.ts @@ -0,0 +1,230 @@ +import { test, expect } from "@playwright/test"; +import * as fse from "fs-extra"; +import path from "path"; +import shell from "shelljs"; +import glob from "glob"; + +import { createFixtureProject, js, json } from "./helpers/create-fixture"; + +let projectDir: string; + +const findBrowserBundle = (projectDir: string): string => + path.resolve(projectDir, "public", "build"); + +const findServerBundle = (projectDir: string): string => + path.resolve(projectDir, "build", "index.js"); + +const importPattern = (importSpecifier: string) => + new RegExp( + String.raw`import\s*{.*}\s*from\s*"` + importSpecifier + String.raw`"` + ); + +const findCodeFiles = async (directory: string) => + glob.sync("**/*.@(js|jsx|ts|tsx)", { + cwd: directory, + absolute: true, + }); +const searchFiles = async (pattern: string | RegExp, files: string[]) => { + let result = shell.grep("-l", pattern, files); + return result.stdout + .trim() + .split("\n") + .filter((line) => line.length > 0); +}; + +test.beforeAll(async () => { + projectDir = await createFixtureProject({ + template: "deno-template", + files: { + "package.json": json({ + private: true, + sideEffects: false, + dependencies: { + "@remix-run/deno": "0.0.0-local-version", + "@remix-run/react": "0.0.0-local-version", + react: "0.0.0-local-version", + "react-dom": "0.0.0-local-version", + component: "0.0.0-local-version", + "deno-pkg": "0.0.0-local-version", + }, + devDependencies: { + "@remix-run/dev": "0.0.0-local-version", + }, + }), + "app/routes/index.jsx": js` + import fake from "deno-pkg"; + import { urlComponent } from "https://deno.land/x/component.ts"; + import { urlUtil } from "https://deno.land/x/util.ts"; + import { urlServerOnly } from "https://deno.land/x/server-only.ts"; + + import { npmComponent } from "npm-component"; + import { npmUtil } from "npm-util"; + import { npmServerOnly } from "npm-server-only"; + + import { useLoaderData } from "@remix-run/react"; + + export const loader = () => { + return json({ + a: urlUtil(), + b: urlServerOnly(), + c: npmUtil(), + d: npmServerOnly(), + }); + } + + export default function Index() { + const data = useLoaderData(); + return ( + + ) + } + `, + "node_modules/npm-component/package.json": json({ + name: "npm-component", + version: "1.0.0", + sideEffects: false, + }), + "node_modules/npm-component/index.js": js` + module.exports = { npmComponent: () => "NPM_COMPONENT" }; + `, + "node_modules/npm-util/package.json": json({ + name: "npm-util", + version: "1.0.0", + sideEffects: false, + }), + "node_modules/npm-util/index.js": js` + module.exports = { npmUtil: () => "NPM_UTIL" }; + `, + "node_modules/npm-server-only/package.json": json({ + name: "npm-server-only", + version: "1.0.0", + sideEffects: false, + }), + "node_modules/npm-server-only/index.js": js` + module.exports = { npmServerOnly: () => "NPM_SERVER_ONLY" }; + `, + "node_modules/deno-pkg/package.json": json({ + name: "deno-pkg", + version: "1.0.0", + type: "module", + main: "./default.js", + exports: { + deno: "./deno.js", + worker: "./worker.js", + default: "./default.js", + }, + sideEffects: false, + }), + "node_modules/deno-pkg/deno.js": js` + export default "DENO_EXPORTS"; + `, + "node_modules/deno-pkg/worker.js": js` + export default "WORKER_EXPORTS"; + `, + "node_modules/deno-pkg/default.js": js` + export default "DEFAULT_EXPORTS"; + `, + }, + }); +}); + +test("compiler does not bundle url imports for server", async () => { + let serverBundle = await fse.readFile(findServerBundle(projectDir), "utf8"); + expect(serverBundle).toMatch(importPattern("https://deno.land/x/util.ts")); + expect(serverBundle).toMatch( + importPattern("https://deno.land/x/server-only.ts") + ); + + // server-side rendering + expect(serverBundle).toMatch( + importPattern("https://deno.land/x/component.ts") + ); +}); + +test("compiler does not bundle url imports for browser", async () => { + let browserBundle = findBrowserBundle(projectDir); + let browserCodeFiles = await findCodeFiles(browserBundle); + + let utilFiles = await searchFiles( + importPattern("https://deno.land/x/util.ts"), + browserCodeFiles + ); + expect(utilFiles.length).toBeGreaterThanOrEqual(1); + + let componentFiles = await searchFiles( + importPattern("https://deno.land/x/component.ts"), + browserCodeFiles + ); + expect(componentFiles.length).toBeGreaterThanOrEqual(1); + + /* + Url imports _could_ have side effects, but the vast majority do not. + Currently Remix marks all URL imports as side-effect free. + */ + let serverOnlyUtilFiles = await searchFiles( + importPattern("https://deno.land/x/server-only.ts"), + browserCodeFiles + ); + expect(serverOnlyUtilFiles.length).toBe(0); +}); + +test("compiler bundles npm imports for server", async () => { + let serverBundle = await fse.readFile(findServerBundle(projectDir), "utf8"); + + expect(serverBundle).not.toMatch(importPattern("npm-component")); + expect(serverBundle).toContain("NPM_COMPONENT"); + + expect(serverBundle).not.toMatch(importPattern("npm-util")); + expect(serverBundle).toContain("NPM_UTIL"); + + expect(serverBundle).not.toMatch(importPattern("npm-server-only")); + expect(serverBundle).toContain("NPM_SERVER_ONLY"); +}); + +test("compiler bundles npm imports for browser", async () => { + let browserBundle = findBrowserBundle(projectDir); + let browserCodeFiles = await findCodeFiles(browserBundle); + + let utilImports = await searchFiles( + importPattern("npm-util"), + browserCodeFiles + ); + expect(utilImports.length).toBe(0); + let utilFiles = await searchFiles("NPM_UTIL", browserCodeFiles); + expect(utilFiles.length).toBeGreaterThanOrEqual(1); + + let componentImports = await searchFiles( + importPattern("npm-component"), + browserCodeFiles + ); + expect(componentImports.length).toBe(0); + let componentFiles = await searchFiles("NPM_COMPONENT", browserCodeFiles); + expect(componentFiles.length).toBeGreaterThanOrEqual(1); + + let serverOnlyImports = await searchFiles( + importPattern("npm-server-only"), + browserCodeFiles + ); + expect(serverOnlyImports.length).toBe(0); + let serverOnlyFiles = await searchFiles("NPM_SERVER_ONLY", browserCodeFiles); + expect(serverOnlyFiles.length).toBe(0); +}); + +test("compiler bundles deno export of 3rd party package", async () => { + let serverBundle = await fse.readFile(findServerBundle(projectDir), "utf8"); + + expect(serverBundle).toMatch("DENO_EXPORTS"); + expect(serverBundle).not.toMatch("DEFAULT_EXPORTS"); +}); diff --git a/integration/helpers/create-fixture.ts b/integration/helpers/create-fixture.ts index f7add1aeabf..94af6ab020c 100644 --- a/integration/helpers/create-fixture.ts +++ b/integration/helpers/create-fixture.ts @@ -18,7 +18,7 @@ interface FixtureInit { buildStdio?: Writable; sourcemap?: boolean; files: { [filename: string]: string }; - template?: "cf-template" | "node-template"; + template?: "cf-template" | "deno-template" | "node-template"; setup?: "node" | "cloudflare"; } diff --git a/integration/helpers/deno-template/.gitignore b/integration/helpers/deno-template/.gitignore new file mode 100644 index 00000000000..2b5c2c32959 --- /dev/null +++ b/integration/helpers/deno-template/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ + +/.cache +/build +/public/build \ No newline at end of file diff --git a/integration/helpers/deno-template/app/entry.client.tsx b/integration/helpers/deno-template/app/entry.client.tsx new file mode 100644 index 00000000000..62a6a81634d --- /dev/null +++ b/integration/helpers/deno-template/app/entry.client.tsx @@ -0,0 +1,5 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import { RemixBrowser } from "@remix-run/react"; + +ReactDOM.hydrate(, document); diff --git a/integration/helpers/deno-template/app/entry.server.tsx b/integration/helpers/deno-template/app/entry.server.tsx new file mode 100644 index 00000000000..5aff7ec014f --- /dev/null +++ b/integration/helpers/deno-template/app/entry.server.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { renderToString } from "react-dom/server"; +import { RemixServer } from "@remix-run/react"; +import type { EntryContext } from "@remix-run/deno"; + +export default function handleRequest( + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, +) { + const markup = renderToString( + , + ); + + responseHeaders.set("Content-Type", "text/html"); + + return new Response("" + markup, { + status: responseStatusCode, + headers: responseHeaders, + }); +} diff --git a/integration/helpers/deno-template/app/root.tsx b/integration/helpers/deno-template/app/root.tsx new file mode 100644 index 00000000000..a74cc0195c0 --- /dev/null +++ b/integration/helpers/deno-template/app/root.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react"; +import type { MetaFunction } from "@remix-run/deno"; + +export const meta: MetaFunction = () => ({ + charset: "utf-8", + title: "New Remix App", + viewport: "width=device-width,initial-scale=1", +}); + +export default function App() { + return ( + + + + + + + + + + + + + ); +} diff --git a/integration/helpers/deno-template/package.json b/integration/helpers/deno-template/package.json new file mode 100644 index 00000000000..1fec877f2bf --- /dev/null +++ b/integration/helpers/deno-template/package.json @@ -0,0 +1,17 @@ +{ + "name": "remix-template-deno", + "private": true, + "sideEffects": false, + "dependencies": { + "@remix-run/deno": "*", + "@remix-run/react": "*", + "react": "^17.0.2", + "react-dom": "^17.0.2" + }, + "devDependencies": { + "@remix-run/dev": "*" + }, + "engines": { + "node": ">=14" + } +} diff --git a/integration/helpers/deno-template/public/favicon.ico b/integration/helpers/deno-template/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8830cf6821b354114848e6354889b8ecf6d2bc61 GIT binary patch literal 16958 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 EKbET!0{{R3 literal 0 HcmV?d00001 diff --git a/integration/helpers/deno-template/remix.config.js b/integration/helpers/deno-template/remix.config.js new file mode 100644 index 00000000000..625a152107a --- /dev/null +++ b/integration/helpers/deno-template/remix.config.js @@ -0,0 +1,12 @@ +module.exports = { + serverBuildTarget: "deno", + server: "./server.ts", + + /* + If live reload causes page to re-render without changes (live reload is too fast), + increase the dev server broadcast delay. + + If live reload seems slow, try to decrease the dev server broadcast delay. + */ + devServerBroadcastDelay: 300, +}; diff --git a/integration/helpers/deno-template/server.ts b/integration/helpers/deno-template/server.ts new file mode 100644 index 00000000000..87e95bf7b18 --- /dev/null +++ b/integration/helpers/deno-template/server.ts @@ -0,0 +1,14 @@ +import { serve } from "https://deno.land/std@0.128.0/http/server.ts"; +import { createRequestHandlerWithStaticFiles } from "@remix-run/deno"; +// Import path interpreted by the Remix compiler +import * as build from "@remix-run/dev/server-build"; + +const remixHandler = createRequestHandlerWithStaticFiles({ + build, + mode: process.env.NODE_ENV, + getLoadContext: () => ({}), +}); + +const port = Number(Deno.env.get("PORT")) || 8000; +console.log(`Listening on http://localhost:${port}`); +serve(remixHandler, { port });