From c5714f7e620cab32e86cb7d047ccc03884c654d3 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Mon, 25 Sep 2023 20:46:17 +0530 Subject: [PATCH 1/7] outputFiles: is undefined --- .../__tests__/defaultOptions.test.ts | 29 +++++++++++++++++-- packages/esbuild-plugin-react18/package.json | 1 + packages/esbuild-plugin-react18/src/index.ts | 3 ++ packages/esbuild-plugin-react18/touchup.js | 21 +++++++------- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts index 888587b2..47f5a3dd 100644 --- a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts +++ b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts @@ -1,8 +1,33 @@ import fs from "node:fs"; import path from "node:path"; -import { describe, test } from "vitest"; +import { describe, test, beforeAll } from "vitest"; +import esbuild from "esbuild"; +import react18Plugin from "../src"; +import glob from "tiny-glob"; -describe.concurrent("Test plugin with default options", () => { +describe.concurrent("Test plugin with default options - CJS build", async () => { + beforeAll(async () => { + await esbuild.build({ + format: "cjs", + target: "es2019", + sourcemap: false, + bundle: true, + minify: true, + plugins: [react18Plugin()], + entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + publicPath: "https://my.domain/static/", + external: ["react", "react-dom"], + outdir: "./dist/default", + metafile: true, + }); + }); + + test("dummy", async ({ expect }) => { + expect("Ok").toBe("Ok"); + }); +}); + +describe.todo("Test plugin with default options", () => { const exampleBuildDir = path.resolve( process.cwd(), "..", diff --git a/packages/esbuild-plugin-react18/package.json b/packages/esbuild-plugin-react18/package.json index f6663a75..b815006f 100644 --- a/packages/esbuild-plugin-react18/package.json +++ b/packages/esbuild-plugin-react18/package.json @@ -16,6 +16,7 @@ "@vitest/coverage-v8": "^0.34.5", "esbuild": "^0.18.17", "octokit": "^3.1.0", + "tiny-glob": "^0.2.9", "typescript": "^5.1.6", "vitest": "^0.34.1" }, diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index 0a48c550..28df4751 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -116,6 +116,8 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ const useClientRegExp = /['"]use client['"]\s?;/i; build.onEnd(result => { + console.log("onEnd -- start", result); + result.outputFiles ?.filter(f => !f.path.endsWith(".map")) .forEach(f => { @@ -144,6 +146,7 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ if (!options?.keepTests) { result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); } + console.log("onEnd", result); }); }, }); diff --git a/packages/esbuild-plugin-react18/touchup.js b/packages/esbuild-plugin-react18/touchup.js index 25a9c3e2..7b315ae8 100644 --- a/packages/esbuild-plugin-react18/touchup.js +++ b/packages/esbuild-plugin-react18/touchup.js @@ -10,16 +10,7 @@ delete packageJson.scripts; packageJson.main = "index.js"; packageJson.types = "index.d.ts"; -fs.writeFileSync( - path.resolve(__dirname, "dist", "package.json"), - JSON.stringify(packageJson, null, 2), -); - -fs.copyFileSync( - path.resolve(__dirname, "..", "..", "README.md"), - path.resolve(__dirname, "dist", "README.md"), -); - +console.log(process.env.TOKEN, process.env.OWNER, process.env.REPO); if (process.env.TOKEN) { const { Octokit } = require("octokit"); // Octokit.js @@ -55,3 +46,13 @@ if (process.env.TOKEN) { console.log("octokit error", e); } } + +fs.writeFileSync( + path.resolve(__dirname, "dist", "package.json"), + JSON.stringify(packageJson, null, 2), +); + +fs.copyFileSync( + path.resolve(__dirname, "..", "..", "README.md"), + path.resolve(__dirname, "dist", "README.md"), +); From cec3e564b1e0a9cd76111784f5e492512d7c5559 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 10:53:42 +0530 Subject: [PATCH 2/7] fix: use write prop from initialOptions to enable outputFiles --- .../__tests__/defaultOptions.test.ts | 1 - packages/esbuild-plugin-react18/src/index.ts | 20 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts index 47f5a3dd..269c92d3 100644 --- a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts +++ b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts @@ -18,7 +18,6 @@ describe.concurrent("Test plugin with default options - CJS build", async () => publicPath: "https://my.domain/static/", external: ["react", "react-dom"], outdir: "./dist/default", - metafile: true, }); }); diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index 28df4751..518fd3ed 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -53,6 +53,10 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ setup(build) { const ignoreNamespace = "mayank1513-ignore-" + uuid(); const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; + + const write = build.initialOptions.write; + build.initialOptions.write = false; + if (!options?.keepTests) { build.onResolve({ filter: testPathRegExp }, args => ({ path: args.path, @@ -109,23 +113,19 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ build.onLoad({ filter: /.*/, namespace: ignoreNamespace }, args => { /** remove content to avoid building/transpiling test files unnecessarily*/ - console.log("onLoad", args); return { contents: "" }; }); const useClientRegExp = /['"]use client['"]\s?;/i; build.onEnd(result => { - console.log("onEnd -- start", result); - result.outputFiles ?.filter(f => !f.path.endsWith(".map")) .forEach(f => { const txt = f.text; if (txt.match(useClientRegExp)) { - Object.defineProperty(f, "text", { - value: '"use client";\n' + txt.replace(useClientRegExp, ""), - }); + const value = '"use client";\n' + txt.replace(useClientRegExp, ""); + f.contents = new TextEncoder().encode(value); } }); @@ -146,7 +146,13 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ if (!options?.keepTests) { result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); } - console.log("onEnd", result); + /** assume true if undefined */ + if (write === undefined || write) { + result.outputFiles?.forEach(file => { + fs.mkdirSync(path.dirname(file.path), { recursive: true }); + fs.writeFileSync(file.path, file.contents); + }); + } }); }, }); From 1c93c2e76c1af20f3d1720385c8d27fecdb41f44 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 15:08:10 +0530 Subject: [PATCH 3/7] add new tests --- .../src/client/star-me/ignore-me.ts | 5 +++ .../__tests__/defaultOptions.test.ts | 35 +++++------------- .../__tests__/ignorePatterns.test.ts | 36 +++++++++++++++++++ packages/esbuild-plugin-react18/src/index.ts | 4 +++ 4 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 packages/esbuild-plugin-react18-example/src/client/star-me/ignore-me.ts create mode 100644 packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts diff --git a/packages/esbuild-plugin-react18-example/src/client/star-me/ignore-me.ts b/packages/esbuild-plugin-react18-example/src/client/star-me/ignore-me.ts new file mode 100644 index 00000000..2133a155 --- /dev/null +++ b/packages/esbuild-plugin-react18-example/src/client/star-me/ignore-me.ts @@ -0,0 +1,5 @@ +// ignore-me based on content + +/** ignore with content pattern */ + +export const IamIgnored = true; diff --git a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts index 269c92d3..46e51bdc 100644 --- a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts +++ b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts @@ -1,39 +1,15 @@ import fs from "node:fs"; import path from "node:path"; import { describe, test, beforeAll } from "vitest"; -import esbuild from "esbuild"; -import react18Plugin from "../src"; -import glob from "tiny-glob"; -describe.concurrent("Test plugin with default options - CJS build", async () => { - beforeAll(async () => { - await esbuild.build({ - format: "cjs", - target: "es2019", - sourcemap: false, - bundle: true, - minify: true, - plugins: [react18Plugin()], - entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), - publicPath: "https://my.domain/static/", - external: ["react", "react-dom"], - outdir: "./dist/default", - }); - }); - - test("dummy", async ({ expect }) => { - expect("Ok").toBe("Ok"); - }); -}); - -describe.todo("Test plugin with default options", () => { +/** testing tsup example - make sure it is build before running this test suit */ +describe("Test plugin with default options in example build with tsup", () => { const exampleBuildDir = path.resolve( process.cwd(), "..", "esbuild-plugin-react18-example", "dist", ); - console.log({ exampleBuildDir }); test(`"use client"; directive should be present in client components`, ({ expect }) => { const text = fs.readFileSync(path.resolve(exampleBuildDir, "client", "index.js"), "utf-8"); expect(/^"use client";\n/m.test(text)).toBe(true); @@ -42,4 +18,11 @@ describe.todo("Test plugin with default options", () => { const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "index.js"), "utf-8"); expect(/^"use client";\n/m.test(text)).toBe(false); }); + test(`should not contain data-testid`, ({ expect }) => { + const text = fs.readFileSync( + path.resolve(exampleBuildDir, "client", "star-me", "star-me.js"), + "utf-8", + ); + expect(/data-testid/.test(text)).toBe(false); + }); }); diff --git a/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts b/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts new file mode 100644 index 00000000..d6fa2459 --- /dev/null +++ b/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, test, beforeAll } from "vitest"; +import esbuild from "esbuild"; +import react18Plugin from "../src"; +import glob from "tiny-glob"; + +describe.concurrent("Test plugin with ignorePatterns -- without content pattern", async () => { + beforeAll(async () => { + await esbuild.build({ + format: "cjs", + target: "es2019", + sourcemap: false, + bundle: true, + minify: true, + plugins: [react18Plugin({ ignorePatterns: [{ pathPattern: /star-me/ }] })], + entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + publicPath: "https://my.domain/static/", + external: ["react", "react-dom"], + outdir: "./dist/default", + }); + }); + + const exampleBuildDir = path.resolve(process.cwd(), "dist", "default"); + test(`"use client"; directive should be present in client components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "client", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(true); + }); + test(`"use client"; directive should not be present in server components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(false); + }); + test(`star-me.tsx file should not exist`, ({ expect }) => { + expect(fs.existsSync(path.resolve(exampleBuildDir, "client", "star-me"))).toBe(false); + }); +}); diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index 518fd3ed..cafd6085 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -78,6 +78,7 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ options?.ignorePatterns?.forEach(ignorePattern => { build.onResolve({ filter: ignorePattern.pathPattern }, args => { /** remove content to avoid building/transpiling test files unnecessarily*/ + console.log("onResolve - ignore", args); if (!ignorePattern.contentPatterns?.length) return { path: args.path, @@ -146,6 +147,9 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ if (!options?.keepTests) { result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); } + + /** remove empty files */ + result.outputFiles = result.outputFiles?.filter(f => f.text !== ""); /** assume true if undefined */ if (write === undefined || write) { result.outputFiles?.forEach(file => { From e1862093c3dfd3c27c4f04a0f6128e8a3f013daf Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 20:12:38 +0530 Subject: [PATCH 4/7] fix: ignorePatterns with content pattern --- .changeset/wild-snakes-wonder.md | 5 ++ .../__tests__/defaultOptions.test.ts | 2 +- .../__tests__/ignorePatterns.test.ts | 47 ++++++++++++++++++- packages/esbuild-plugin-react18/src/index.ts | 40 +++++++++------- 4 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 .changeset/wild-snakes-wonder.md diff --git a/.changeset/wild-snakes-wonder.md b/.changeset/wild-snakes-wonder.md new file mode 100644 index 00000000..4bd99405 --- /dev/null +++ b/.changeset/wild-snakes-wonder.md @@ -0,0 +1,5 @@ +--- +"esbuild-plugin-react18": patch +--- + +Fix ignorePatterns with contentPatterns diff --git a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts index 46e51bdc..e1620a3c 100644 --- a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts +++ b/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts @@ -3,7 +3,7 @@ import path from "node:path"; import { describe, test, beforeAll } from "vitest"; /** testing tsup example - make sure it is build before running this test suit */ -describe("Test plugin with default options in example build with tsup", () => { +describe.concurrent("Test plugin with default options in example build with tsup", () => { const exampleBuildDir = path.resolve( process.cwd(), "..", diff --git a/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts b/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts index d6fa2459..9f9ae11e 100644 --- a/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts +++ b/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts @@ -6,6 +6,11 @@ import react18Plugin from "../src"; import glob from "tiny-glob"; describe.concurrent("Test plugin with ignorePatterns -- without content pattern", async () => { + const outDir = "ignore-patterns-0"; + const exampleBuildDir = path.resolve(process.cwd(), "dist", outDir); + try { + fs.unlinkSync(path.resolve(exampleBuildDir)); + } catch {} beforeAll(async () => { await esbuild.build({ format: "cjs", @@ -17,11 +22,10 @@ describe.concurrent("Test plugin with ignorePatterns -- without content pattern" entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), publicPath: "https://my.domain/static/", external: ["react", "react-dom"], - outdir: "./dist/default", + outdir: "./dist/" + outDir, }); }); - const exampleBuildDir = path.resolve(process.cwd(), "dist", "default"); test(`"use client"; directive should be present in client components`, ({ expect }) => { const text = fs.readFileSync(path.resolve(exampleBuildDir, "client", "index.js"), "utf-8"); expect(/^"use client";\n/m.test(text)).toBe(true); @@ -34,3 +38,42 @@ describe.concurrent("Test plugin with ignorePatterns -- without content pattern" expect(fs.existsSync(path.resolve(exampleBuildDir, "client", "star-me"))).toBe(false); }); }); + +/** + * When content pattern is provided only the ignorePattern files having content matching the content pattern will be removed + */ +describe.concurrent("Test plugin with ignorePatterns with content pattern", async () => { + const outDir = "ignore-patterns-1"; + beforeAll(async () => { + await esbuild.build({ + format: "cjs", + target: "es2019", + sourcemap: false, + bundle: true, + minify: true, + plugins: [ + react18Plugin({ + ignorePatterns: [{ pathPattern: /star-me/, contentPatterns: [/ignore-me/] }], + }), + ], + entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + publicPath: "https://my.domain/static/", + external: ["react", "react-dom"], + outdir: "./dist/" + outDir, + }); + }); + + const exampleBuildDir = path.resolve(process.cwd(), "dist", outDir); + test(`star-me.tsx file should exist`, ({ expect }) => { + expect(fs.existsSync(path.resolve(exampleBuildDir, "client", "star-me", "star-me.js"))).toBe( + true, + ); + }); + test(`ignore-me.ts file should not exist as it contains content "ignore-me" (Note: path pattern is still star-me)`, ({ + expect, + }) => { + expect(fs.existsSync(path.resolve(exampleBuildDir, "client", "star-me", "ignore-me.js"))).toBe( + false, + ); + }); +}); diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index cafd6085..fc36c7da 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -3,10 +3,10 @@ import fs from "node:fs"; import path from "node:path"; type React18PluginOptions = { - /** do not ignore tese files */ + /** to not ignore tese files */ keepTests?: boolean; - /** do not remove `data-testid` attributes. If `keepTests` is true, + /** to not remove `data-testid` attributes. If `keepTests` is true, * `data-testid` attributes will not be removed irrespective of * `keepTestIds` value. * This attribute is useful when setting `sourceReplacePatterns` @@ -52,6 +52,7 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ name: "esbuild-plugin-react18-" + uuid(), setup(build) { const ignoreNamespace = "mayank1513-ignore-" + uuid(); + const keepNamespace = "mayank1513-keep-" + uuid(); const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; const write = build.initialOptions.write; @@ -78,22 +79,19 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ options?.ignorePatterns?.forEach(ignorePattern => { build.onResolve({ filter: ignorePattern.pathPattern }, args => { /** remove content to avoid building/transpiling test files unnecessarily*/ - console.log("onResolve - ignore", args); - if (!ignorePattern.contentPatterns?.length) - return { - path: args.path, - namespace: ignoreNamespace, - }; - const text = fs.readFileSync(path.resolve(args.resolveDir, args.path), "utf8"); - for (const contentPattern of ignorePattern.contentPatterns) { - if (contentPattern.test(text)) { - return { - path: args.path, - namespace: ignoreNamespace, - }; + const fullPath = path.resolve(args.resolveDir, args.path); + if (!ignorePattern.contentPatterns?.length || !fs.existsSync(fullPath)) + return { path: args.path, namespace: ignoreNamespace }; + + if (!fs.lstatSync(fullPath).isDirectory()) { + const text = fs.readFileSync(fullPath, "utf8"); + for (const contentPattern of ignorePattern.contentPatterns) { + if (contentPattern.test(text)) { + return { path: args.path, namespace: ignoreNamespace }; + } } } - return { path: args.path }; + return { path: fullPath, namespace: keepNamespace }; }); }); @@ -117,6 +115,16 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ return { contents: "" }; }); + build.onLoad({ filter: /.*/, namespace: keepNamespace }, args => { + console.log("keep", args); + if (fs.existsSync(args.path) && fs.lstatSync(args.path).isDirectory()) + return { contents: "" }; + else { + const loader = args.path.slice(args.path.lastIndexOf(".") + 1); + return { contents: fs.readFileSync(args.path, "utf-8"), loader } as OnLoadResult; + } + }); + const useClientRegExp = /['"]use client['"]\s?;/i; build.onEnd(result => { From e5d3a58ec2c391fd85e20ea5b68ba63546e0c03f Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 20:52:16 +0530 Subject: [PATCH 5/7] sourceReplacePatterns --- .changeset/chatty-wasps-report.md | 5 ++ .../src/server/constants.ts | 3 ++ .../src/server/fork-me/fork-me.tsx | 5 +- .../__tests__/sourceReplacePatterns.test.ts | 53 +++++++++++++++++++ packages/esbuild-plugin-react18/src/index.ts | 28 +++++----- 5 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 .changeset/chatty-wasps-report.md create mode 100644 packages/esbuild-plugin-react18-example/src/server/constants.ts create mode 100644 packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts diff --git a/.changeset/chatty-wasps-report.md b/.changeset/chatty-wasps-report.md new file mode 100644 index 00000000..b36bee0c --- /dev/null +++ b/.changeset/chatty-wasps-report.md @@ -0,0 +1,5 @@ +--- +"esbuild-plugin-react18": patch +--- + +remove data-testid as last item in sourceReplacePatterns. This will reduce the onLoad conflicts significantly as this pattern matches all the js, ts, jsx and tsx files. diff --git a/packages/esbuild-plugin-react18-example/src/server/constants.ts b/packages/esbuild-plugin-react18-example/src/server/constants.ts new file mode 100644 index 00000000..b231c4a2 --- /dev/null +++ b/packages/esbuild-plugin-react18-example/src/server/constants.ts @@ -0,0 +1,3 @@ +/** we will replace default colors to test sourceReplacePatterns */ +export const defaultBgColor = "#aaa"; +export const defaultColor = "#555"; diff --git a/packages/esbuild-plugin-react18-example/src/server/fork-me/fork-me.tsx b/packages/esbuild-plugin-react18-example/src/server/fork-me/fork-me.tsx index 33676886..0284ca3a 100644 --- a/packages/esbuild-plugin-react18-example/src/server/fork-me/fork-me.tsx +++ b/packages/esbuild-plugin-react18-example/src/server/fork-me/fork-me.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import cssClasses from "./fork-me.module.css"; +import { defaultBgColor, defaultColor } from "../constants"; interface ForkMeProps { gitHubUrl: string; @@ -29,8 +30,8 @@ export function ForkMe({ }: ForkMeProps) { const w = (Number.isNaN(Number(width)) ? width : `${width}px`) || "15em"; const h = (Number.isNaN(Number(height)) ? height : `${height}px`) || "35px"; - const bgC = bgColor || "#aaa"; - const tC = textColor || "#555"; + const bgC = bgColor || defaultBgColor; + const tC = textColor || defaultColor; const style = { "--w": w, "--h": h, diff --git a/packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts b/packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts new file mode 100644 index 00000000..43c2e9b4 --- /dev/null +++ b/packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts @@ -0,0 +1,53 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, test, beforeAll } from "vitest"; +import esbuild from "esbuild"; +import react18Plugin from "../src"; +import glob from "tiny-glob"; + +describe.concurrent("Test plugin with ignorePatterns -- without content pattern", async () => { + const outDir = "source-replace-patterns"; + const exampleBuildDir = path.resolve(process.cwd(), "dist", outDir); + try { + fs.unlinkSync(path.resolve(exampleBuildDir)); + } catch {} + beforeAll(async () => { + await esbuild.build({ + format: "cjs", + target: "es2019", + sourcemap: false, + bundle: true, + minify: true, + plugins: [ + react18Plugin({ + sourceReplacePatterns: [ + { + pathPattern: /constants/, + replaceParams: [ + { pattern: /aaa/, substitute: "3c3c3c" }, + { pattern: /#555/, substitute: "#ccc" }, + ], + }, + ], + }), + ], + entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + publicPath: "https://my.domain/static/", + external: ["react", "react-dom"], + outdir: "./dist/" + outDir, + }); + }); + + test(`"use client"; directive should be present in client components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "client", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(true); + }); + test(`"use client"; directive should not be present in server components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(false); + }); + test(`defaultBgColor should be "#3c3c3c" and defaultColor should be "#ccc"`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "constants.js"), "utf-8"); + expect(text.includes("3c3c3c")).toBe(true); + }); +}); diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index fc36c7da..e8427bc2 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -48,9 +48,10 @@ type React18PluginOptions = { }; /** This plugin prevents building test files by esbuild. DTS may still geenrate type files for the tests with only { } as file content*/ -const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ +const react18Plugin: (options: React18PluginOptions) => Plugin = options => ({ name: "esbuild-plugin-react18-" + uuid(), setup(build) { + if (!options) options = {}; const ignoreNamespace = "mayank1513-ignore-" + uuid(); const keepNamespace = "mayank1513-keep-" + uuid(); const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; @@ -58,25 +59,22 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ const write = build.initialOptions.write; build.initialOptions.write = false; - if (!options?.keepTests) { + if (!options.keepTests) { build.onResolve({ filter: testPathRegExp }, args => ({ path: args.path, namespace: ignoreNamespace, })); - if (!options?.keepTestIds) { + if (!options.keepTestIds) { /** remove data-testid */ - build.onLoad({ filter: /.*\.(j|t)s(x)?$/, namespace: "file" }, args => { - const text = fs.readFileSync(args.path, "utf8"); - const loader = args.path.slice(args.path.lastIndexOf(".") + 1); - return { - contents: text.replace(/\s*data-testid="[^"]*"/gm, " "), - loader, - } as OnLoadResult; + if (!options.sourceReplacePatterns) options.sourceReplacePatterns = []; + options.sourceReplacePatterns.push({ + pathPattern: /.*\.(j|t)s(x)?$/, + replaceParams: [{ pattern: /\s*data-testid="[^"]*"/gm, substitute: " " }], }); } } - options?.ignorePatterns?.forEach(ignorePattern => { + options.ignorePatterns?.forEach(ignorePattern => { build.onResolve({ filter: ignorePattern.pathPattern }, args => { /** remove content to avoid building/transpiling test files unnecessarily*/ const fullPath = path.resolve(args.resolveDir, args.path); @@ -95,7 +93,8 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ }); }); - options?.sourceReplacePatterns?.forEach(sourceReplacePattern => { + options.sourceReplacePatterns?.forEach(sourceReplacePattern => { + if (sourceReplacePattern.replaceParams.length === 0) return; /** Add namespace file to avoid conflict with ignored files */ build.onLoad({ filter: sourceReplacePattern.pathPattern, namespace: "file" }, args => { let contents = fs.readFileSync(args.path, "utf8"); @@ -116,7 +115,6 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ }); build.onLoad({ filter: /.*/, namespace: keepNamespace }, args => { - console.log("keep", args); if (fs.existsSync(args.path) && fs.lstatSync(args.path).isDirectory()) return { contents: "" }; else { @@ -139,7 +137,7 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ }); /** handle buildReplacePatterns */ - options?.buildReplacePatterns?.forEach(buildReplacePattern => { + options.buildReplacePatterns?.forEach(buildReplacePattern => { result.outputFiles ?.filter(f => buildReplacePattern.pathPattern.test(f.path)) .forEach(f => { @@ -152,7 +150,7 @@ const react18Plugin: (options?: React18PluginOptions) => Plugin = options => ({ }); /** Do not generate {empty} test files if keepTests is not set to true */ - if (!options?.keepTests) { + if (!options.keepTests) { result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); } From ddad9ed9270a6adf4f18e0cd844c1477ac067b9b Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 21:03:28 +0530 Subject: [PATCH 6/7] fix: buildReplacePatterns --- .changeset/chatty-wasps-report.md | 5 -- .changeset/wild-snakes-wonder.md | 5 -- packages/esbuild-plugin-react18/CHANGELOG.md | 8 +++ .../__tests__/buildReplacePatterns.test.ts | 56 +++++++++++++++++++ packages/esbuild-plugin-react18/package.json | 2 +- packages/esbuild-plugin-react18/src/index.ts | 6 +- 6 files changed, 68 insertions(+), 14 deletions(-) delete mode 100644 .changeset/chatty-wasps-report.md delete mode 100644 .changeset/wild-snakes-wonder.md create mode 100644 packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts diff --git a/.changeset/chatty-wasps-report.md b/.changeset/chatty-wasps-report.md deleted file mode 100644 index b36bee0c..00000000 --- a/.changeset/chatty-wasps-report.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"esbuild-plugin-react18": patch ---- - -remove data-testid as last item in sourceReplacePatterns. This will reduce the onLoad conflicts significantly as this pattern matches all the js, ts, jsx and tsx files. diff --git a/.changeset/wild-snakes-wonder.md b/.changeset/wild-snakes-wonder.md deleted file mode 100644 index 4bd99405..00000000 --- a/.changeset/wild-snakes-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"esbuild-plugin-react18": patch ---- - -Fix ignorePatterns with contentPatterns diff --git a/packages/esbuild-plugin-react18/CHANGELOG.md b/packages/esbuild-plugin-react18/CHANGELOG.md index ac75acde..7c95845b 100644 --- a/packages/esbuild-plugin-react18/CHANGELOG.md +++ b/packages/esbuild-plugin-react18/CHANGELOG.md @@ -1,5 +1,13 @@ # esbuild-plugin-react18 +## 0.0.3 + +### Patch Changes + +- e5d3a58: remove data-testid as last item in sourceReplacePatterns. This will reduce the onLoad conflicts significantly as this pattern matches all the js, ts, jsx and tsx files. +- Fix buildReplacePatterns to work on all esbuild setups +- e186209: Fix ignorePatterns with contentPatterns + ## 0.0.2 ### Patch Changes diff --git a/packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts b/packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts new file mode 100644 index 00000000..29f36305 --- /dev/null +++ b/packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts @@ -0,0 +1,56 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, test, beforeAll } from "vitest"; +import esbuild from "esbuild"; +import react18Plugin from "../src"; +import glob from "tiny-glob"; + +/** + * buildReplacePatterns could be very helpful in removing unnecessary comments introduced while bundling from other libraries + */ +describe.concurrent("Test plugin with ignorePatterns -- without content pattern", async () => { + const outDir = "build-replace-patterns"; + const exampleBuildDir = path.resolve(process.cwd(), "dist", outDir); + try { + fs.unlinkSync(path.resolve(exampleBuildDir)); + } catch {} + beforeAll(async () => { + await esbuild.build({ + format: "cjs", + target: "es2019", + sourcemap: false, + bundle: true, + minify: true, + plugins: [ + react18Plugin({ + buildReplacePatterns: [ + { + pathPattern: /constants/, + replaceParams: [ + { pattern: /aaa/, substitute: "3c3c3c" }, + { pattern: /#555/, substitute: "#ccc" }, + ], + }, + ], + }), + ], + entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + publicPath: "https://my.domain/static/", + external: ["react", "react-dom"], + outdir: "./dist/" + outDir, + }); + }); + + test(`"use client"; directive should be present in client components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "client", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(true); + }); + test(`"use client"; directive should not be present in server components`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "index.js"), "utf-8"); + expect(/^"use client";\n/m.test(text)).toBe(false); + }); + test(`defaultBgColor should be "#3c3c3c" and defaultColor should be "#ccc"`, ({ expect }) => { + const text = fs.readFileSync(path.resolve(exampleBuildDir, "server", "constants.js"), "utf-8"); + expect(text.includes("3c3c3c")).toBe(true); + }); +}); diff --git a/packages/esbuild-plugin-react18/package.json b/packages/esbuild-plugin-react18/package.json index b815006f..98aaf136 100644 --- a/packages/esbuild-plugin-react18/package.json +++ b/packages/esbuild-plugin-react18/package.json @@ -2,7 +2,7 @@ "name": "esbuild-plugin-react18", "author": "Mayank Kumar Chaudhari ", "private": false, - "version": "0.0.2", + "version": "0.0.3", "description": "Unleash the Power of React Server Components! ESBuild plugin to build RSC (React18 Server Components) compatible libraries.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts index e8427bc2..3966bd57 100644 --- a/packages/esbuild-plugin-react18/src/index.ts +++ b/packages/esbuild-plugin-react18/src/index.ts @@ -131,8 +131,8 @@ const react18Plugin: (options: React18PluginOptions) => Plugin = options => ({ .forEach(f => { const txt = f.text; if (txt.match(useClientRegExp)) { - const value = '"use client";\n' + txt.replace(useClientRegExp, ""); - f.contents = new TextEncoder().encode(value); + const text = '"use client";\n' + txt.replace(useClientRegExp, ""); + f.contents = new TextEncoder().encode(text); } }); @@ -145,7 +145,7 @@ const react18Plugin: (options: React18PluginOptions) => Plugin = options => ({ buildReplacePattern.replaceParams.forEach(({ pattern, substitute }) => { text = text.replace(pattern, substitute); }); - Object.defineProperty(f, "text", { value: text }); + f.contents = new TextEncoder().encode(text); }); }); From f3a28d16d669b60d0eacd379adfd304da89372b2 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Sat, 30 Sep 2023 21:05:55 +0530 Subject: [PATCH 7/7] update README and tests: add codecov --- .github/workflows/test.yml | 2 +- README.md | 2 +- examples/nextjs/package.json | 1 + examples/vite/package.json | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8c08f64..ac7b872d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18 - - run: npm i -g pnpm && pnpm i --ignore-scripts + - run: npm i -g pnpm && pnpm i name: Install dependencies - run: pnpm build --filter esbuild-plugin-react18-example name: build example app to run tests diff --git a/README.md b/README.md index 520acd62..c6dad050 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# esbuild-plugin-react18 [![test](https://github.com/mayank1513/esbuild-plugin-react18/actions/workflows/test.yml/badge.svg)](https://github.com/mayank1513/esbuild-plugin-react18/actions/workflows/test.yml) [![Version](https://img.shields.io/npm/v/esbuild-plugin-react18.svg?colorB=green)](https://www.npmjs.com/package/esbuild-plugin-react18) [![Downloads](https://img.jsdelivr.com/img.shields.io/npm/dt/esbuild-plugin-react18.svg)](https://www.npmjs.com/package/esbuild-plugin-react18) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/esbuild-plugin-react18) +# esbuild-plugin-react18 [![test](https://github.com/mayank1513/esbuild-plugin-react18/actions/workflows/test.yml/badge.svg)](https://github.com/mayank1513/esbuild-plugin-react18/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/mayank1513/esbuild-plugin-react18/graph/badge.svg)](https://codecov.io/gh/mayank1513/esbuild-plugin-react18) [![Version](https://img.shields.io/npm/v/esbuild-plugin-react18.svg?colorB=green)](https://www.npmjs.com/package/esbuild-plugin-react18) [![Downloads](https://img.jsdelivr.com/img.shields.io/npm/dt/esbuild-plugin-react18.svg)](https://www.npmjs.com/package/esbuild-plugin-react18) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/esbuild-plugin-react18) diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index f5955376..e334ba72 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -20,6 +20,7 @@ "@types/node": "^20.6.3", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "esbuild-plugin-react18": "workspace:^", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", "typescript": "^5.2.2" diff --git a/examples/vite/package.json b/examples/vite/package.json index e903d76e..76022c14 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -22,6 +22,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/parser": "^6.7.2", "@vitejs/plugin-react-swc": "^3.3.2", + "esbuild-plugin-react18": "workspace:^", "eslint": "^8.50.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3",