diff --git a/packages/example-app/package.json b/packages/example-app/package.json index d5c4852..c4cd3de 100644 --- a/packages/example-app/package.json +++ b/packages/example-app/package.json @@ -10,8 +10,6 @@ }, "dependencies": { "@remastered/vercel": "^0.1.21", - "@swc-node/register": "^1.2.1", - "@swc/core": "^1.2.55", "esm": "^3.2.25", "history": "^5.0.0", "lodash": "^4.17.21", diff --git a/packages/remastered/package.json b/packages/remastered/package.json index 5709e1d..9dfd034 100644 --- a/packages/remastered/package.json +++ b/packages/remastered/package.json @@ -34,7 +34,6 @@ "dist" ], "dependencies": { - "@swc/core": "^1.2.57", "@vitejs/plugin-react-refresh": "^1.3.1", "acorn-walk": "^8.1.0", "cmd-ts": "^0.6.9", diff --git a/packages/remastered/src/vite-plugins/routeTransformers.spec.ts b/packages/remastered/src/vite-plugins/routeTransformers.spec.ts new file mode 100644 index 0000000..bcc3cbf --- /dev/null +++ b/packages/remastered/src/vite-plugins/routeTransformers.spec.ts @@ -0,0 +1,30 @@ +import { transform } from "./routeTransformers"; + +test("it removes the named exports", () => { + const code = ` + import a from 'a'; + import b from 'b'; + + type A = T; + + export const loader = "yes"; + export const handle = "ho"; + export function myFunction() {} + export default "Hey there"; + `; + + const result = transform(code, "filename.tsx"); + expect(result?.code).toMatchInlineSnapshot(` + " + import a from 'a'; + import b from 'b'; + + type A = T; + + + export const handle = \\"ho\\"; + + export default \\"Hey there\\"; + " + `); +}); diff --git a/packages/remastered/src/vite-plugins/routeTransformers.ts b/packages/remastered/src/vite-plugins/routeTransformers.ts index 09d0d98..fcf5508 100644 --- a/packages/remastered/src/vite-plugins/routeTransformers.ts +++ b/packages/remastered/src/vite-plugins/routeTransformers.ts @@ -1,8 +1,11 @@ -import { Module, parse, print } from "@swc/core"; import fs from "fs-extra"; import path from "path"; import { PluginOption, ResolvedConfig } from "vite"; import globby from "globby"; +import { parse as parseWithBabel } from "@babel/core"; +import type { ParserPlugin } from "@babel/parser"; +import traverse from "@babel/traverse"; +import MagicString, { SourceMap } from "magic-string"; function isRoute(rootPath: string, filepath: string) { const routesPath = path.join(rootPath, "./app/routes/"); @@ -14,11 +17,6 @@ function isRoute(rootPath: string, filepath: string) { /** * Removes all the `export const ...` from routes, so it won't use server side stuff in client side - * - * TODO this code uses SWC. Maybe we should use Babel? - * We also use `print` by SWC which I do not want. I would want to only parse using SWC. - * Unfortunately, the spans SWC produces do not reflect the actual code we send to it. - * Meaning that if we use MagicString with it, it blows up: https://github.com/swc-project/swc/issues/1366 */ export function routeTransformers(): PluginOption[] { const acceptSelfCode = ` @@ -64,42 +62,7 @@ export function routeTransformers(): PluginOption[] { return null; } - const body: Module["body"] = []; - - const parsed = await parse(code, { - syntax: "typescript", - tsx: true, - dynamicImport: true, - target: "es2020", - }); - - for (const item of parsed.body) { - if (item.type !== "ExportDeclaration") { - body.push(item); - } else if (item.declaration.type === "VariableDeclaration") { - const declarations = item.declaration.declarations.filter( - (declaration) => { - return ( - declaration.id.type === "Identifier" && - ["handle", "meta"].includes(declaration.id.value) - ); - } - ); - if (declarations.length) { - body.push({ - ...item, - declaration: { - ...item.declaration, - declarations, - }, - }); - } - } - } - - const result = await print({ ...parsed, body }); - result.code = result.code; - return result; + return transform(code, id); }, async handleHotUpdate(ctx) { ctx.server.ws.send({ @@ -111,3 +74,68 @@ export function routeTransformers(): PluginOption[] { }, ]; } + +export function transform( + code: string, + filename: string +): null | { code: string; map: SourceMap } { + const parserPlugins: ParserPlugin[] = [ + "importMeta", + // since the plugin now applies before esbuild transforms the code, + // we need to enable some stage 3 syntax since they are supported in + // TS and some environments already. + "topLevelAwait", + "classProperties", + "classPrivateProperties", + "classPrivateMethods", + ]; + + if (filename.endsWith("x")) { + parserPlugins.push("jsx"); + } + + if (/\.tsx?$/.test(filename)) { + parserPlugins.push("typescript"); + } + + const magicString = new MagicString(code); + + const program = parseWithBabel(code, { + filename, + sourceType: "module", + parserOpts: { + plugins: parserPlugins, + }, + }); + + if (!program) { + return null; + } + + const allowedExports = ["handle", "meta"]; + + traverse(program, { + ExportNamedDeclaration(node) { + const declaration = node.get("declaration").node; + if (declaration?.type === "FunctionDeclaration") { + if (!allowedExports.includes(declaration.id?.name!)) { + magicString.remove(node.node.start!, node.node.end!); + } + } else if (declaration?.type === "VariableDeclaration") { + let shouldRemoveEntireNode = true; + for (const decl of declaration.declarations) { + if (allowedExports.includes((decl.id as any).name)) { + shouldRemoveEntireNode = false; + } else if (decl.start && decl.end) { + magicString.remove(decl.start, decl.end); + } + } + if (shouldRemoveEntireNode) { + magicString.remove(node.node.start!, node.node.end!); + } + } + }, + }); + + return { code: magicString.toString(), map: magicString.generateMap() }; +} diff --git a/yarn.lock b/yarn.lock index b3c0bb1..dfda2d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1654,24 +1654,6 @@ dependencies: "@swc-node/core" "^1.5.0" -"@swc-node/register@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@swc-node/register/-/register-1.2.1.tgz#626ecd05ba845a3a08a40be248500e41db1023d2" - integrity sha512-gq0MzvA1xfyc2q7nBInpC08jeL/4/Jk0BGttHSGEYAayMPsW9CCpC/Ejaw2M351DlxxpkUZz23srYAv4oeXnAg== - dependencies: - "@swc-node/core" "^1.4.0" - "@swc-node/sourcemap-support" "^0.1.8" - chalk "^4.1.1" - debug "^4.3.1" - pirates "^4.0.1" - -"@swc-node/sourcemap-support@^0.1.8": - version "0.1.8" - resolved "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.1.8.tgz#8cf74175ae5e3374612011e7e75c03637019db4c" - integrity sha512-AOH32yNN8UJh6Ayc+r3mnPdrjqqEjtXr9wsEiEhh3OqJWFXqkMOHC+18FYhHdTzGzhaYqUshQONjqOTC38yx7Q== - dependencies: - source-map-support "^0.5.19" - "@swc/core-android-arm64@^1.2.57": version "1.2.57" resolved "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.57.tgz#95d7631cbe85ee0abc1774719454217a49bbd2d2" @@ -1762,7 +1744,7 @@ resolved "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.58.tgz#470e856affe53cfd786665a1c932b25dd67e0ed1" integrity sha512-HPmxovhC7DbNcXLJe5nUmo+4o6Ea2d7oFdli3IvTgDri0IynQaRlfVWIuNnZmEsN7Gl1kW7PUK5WZXPUosMn8A== -"@swc/core@^1.2.54", "@swc/core@^1.2.55", "@swc/core@^1.2.57": +"@swc/core@^1.2.54": version "1.2.57" resolved "https://registry.npmjs.org/@swc/core/-/core-1.2.57.tgz#ee5b4e2c907e5e9998c00e430e0f3a4e8c5ad866" integrity sha512-sO6jXmBaiP33L/y6thxa1Dx8WS0Ttg19qsr3SB9/LIovydCSNTsYuLElftZWeq8QoDBwcYCDsiYIPnfZzsRiMg== @@ -2785,7 +2767,7 @@ chalk@^2.0.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== @@ -8635,7 +8617,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.17, source-map-support@^0.5.19, source-map-support@^0.5.6: +source-map-support@^0.5.17, source-map-support@^0.5.6: version "0.5.19" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==