From 32a139df0f4859662b515cc58c89ca0287429ab7 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Wed, 13 Mar 2024 20:56:10 +0530 Subject: [PATCH 01/10] Move library to root --- .../CHANGELOG.md | 0 .../__tests__/buildReplacePatterns.test.ts | 2 +- .../__tests__/defaultOptions.test.ts | 1 + .../__tests__/ignorePatterns.test.ts | 4 ++-- .../__tests__/sourceReplacePatterns.test.ts | 2 +- .../package.json | 0 .../scope.js | 6 +++--- .../src/index.ts | 0 .../touchup.js | 2 +- .../tsconfig.json | 0 pnpm-workspace.yaml | 1 + 11 files changed, 10 insertions(+), 8 deletions(-) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/CHANGELOG.md (100%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/__tests__/buildReplacePatterns.test.ts (95%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/__tests__/defaultOptions.test.ts (98%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/__tests__/ignorePatterns.test.ts (93%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/__tests__/sourceReplacePatterns.test.ts (95%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/package.json (100%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/scope.js (81%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/src/index.ts (100%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/touchup.js (96%) rename {packages/esbuild-plugin-react18 => esbuild-plugin-react18}/tsconfig.json (100%) diff --git a/packages/esbuild-plugin-react18/CHANGELOG.md b/esbuild-plugin-react18/CHANGELOG.md similarity index 100% rename from packages/esbuild-plugin-react18/CHANGELOG.md rename to esbuild-plugin-react18/CHANGELOG.md diff --git a/packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts b/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts similarity index 95% rename from packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts rename to esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts index 6e005a07..c38f2fbb 100644 --- a/packages/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts +++ b/esbuild-plugin-react18/__tests__/buildReplacePatterns.test.ts @@ -34,7 +34,7 @@ describe.concurrent("Test plugin with ignorePatterns -- without content pattern" ], }), ], - entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + entryPoints: await glob("../packages/esbuild-plugin-react18-example/src/**/*.*"), publicPath: "https://my.domain/static/", external: ["react", "react-dom"], outdir: "./test-build/" + outDir, diff --git a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts b/esbuild-plugin-react18/__tests__/defaultOptions.test.ts similarity index 98% rename from packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts rename to esbuild-plugin-react18/__tests__/defaultOptions.test.ts index e1620a3c..f21bc202 100644 --- a/packages/esbuild-plugin-react18/__tests__/defaultOptions.test.ts +++ b/esbuild-plugin-react18/__tests__/defaultOptions.test.ts @@ -7,6 +7,7 @@ describe.concurrent("Test plugin with default options in example build with tsup const exampleBuildDir = path.resolve( process.cwd(), "..", + "packages", "esbuild-plugin-react18-example", "dist", ); diff --git a/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts b/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts similarity index 93% rename from packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts rename to esbuild-plugin-react18/__tests__/ignorePatterns.test.ts index cd35dd39..b8515e31 100644 --- a/packages/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts +++ b/esbuild-plugin-react18/__tests__/ignorePatterns.test.ts @@ -19,7 +19,7 @@ describe.concurrent("Test plugin with ignorePatterns -- without content pattern" bundle: true, minify: true, plugins: [react18Plugin({ ignorePatterns: [{ pathPattern: /star-me/ }] })], - entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + entryPoints: await glob("../packages/esbuild-plugin-react18-example/src/**/*.*"), publicPath: "https://my.domain/static/", external: ["react", "react-dom"], outdir: "./test-build/" + outDir, @@ -56,7 +56,7 @@ describe.concurrent("Test plugin with ignorePatterns with content pattern", asyn ignorePatterns: [{ pathPattern: /star-me/, contentPatterns: [/ignore-me/] }], }), ], - entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + entryPoints: await glob("../packages/esbuild-plugin-react18-example/src/**/*.*"), publicPath: "https://my.domain/static/", external: ["react", "react-dom"], outdir: "./test-build/" + outDir, diff --git a/packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts b/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts similarity index 95% rename from packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts rename to esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts index 0899f53c..52358dc5 100644 --- a/packages/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts +++ b/esbuild-plugin-react18/__tests__/sourceReplacePatterns.test.ts @@ -35,7 +35,7 @@ describe.concurrent("Test plugin with ignorePatterns -- without content pattern" ], }), ], - entryPoints: await glob("../esbuild-plugin-react18-example/src/**/*.*"), + entryPoints: await glob("../packages/esbuild-plugin-react18-example/src/**/*.*"), publicPath: "https://my.domain/static/", external: ["react", "react-dom"], outdir: "./test-build/" + outDir, diff --git a/packages/esbuild-plugin-react18/package.json b/esbuild-plugin-react18/package.json similarity index 100% rename from packages/esbuild-plugin-react18/package.json rename to esbuild-plugin-react18/package.json diff --git a/packages/esbuild-plugin-react18/scope.js b/esbuild-plugin-react18/scope.js similarity index 81% rename from packages/esbuild-plugin-react18/scope.js rename to esbuild-plugin-react18/scope.js index 43598f88..1c622ee7 100644 --- a/packages/esbuild-plugin-react18/scope.js +++ b/esbuild-plugin-react18/scope.js @@ -15,9 +15,9 @@ if (!ref.startsWith(`@${owner}`)) { ); const readMePath = path.resolve(process.cwd(), "README.md"); let readMe = fs.readFileSync(readMePath, { encoding: "utf8" }); - const tmp = "!--|--!"; - readMe = readMe.replace(new RegExp(`$${owner}/${ref}`, "g"), tmp); + const tmp = "!---!"; + readMe = readMe.replace(new RegExp(`${owner}/${ref}`, "g"), tmp); readMe = readMe.replace(new RegExp(ref, "g"), packageJson.name); - readMe = readMe.replace(new RegExp(tmp, "g"), `$${owner}/${ref}`); + readMe = readMe.replace(new RegExp(tmp, "g"), `${owner}/${ref}`); fs.writeFileSync(readMePath, readMe); } diff --git a/packages/esbuild-plugin-react18/src/index.ts b/esbuild-plugin-react18/src/index.ts similarity index 100% rename from packages/esbuild-plugin-react18/src/index.ts rename to esbuild-plugin-react18/src/index.ts diff --git a/packages/esbuild-plugin-react18/touchup.js b/esbuild-plugin-react18/touchup.js similarity index 96% rename from packages/esbuild-plugin-react18/touchup.js rename to esbuild-plugin-react18/touchup.js index 7b315ae8..46cc697d 100644 --- a/packages/esbuild-plugin-react18/touchup.js +++ b/esbuild-plugin-react18/touchup.js @@ -53,6 +53,6 @@ fs.writeFileSync( ); fs.copyFileSync( - path.resolve(__dirname, "..", "..", "README.md"), + path.resolve(__dirname, "..", "README.md"), path.resolve(__dirname, "dist", "README.md"), ); diff --git a/packages/esbuild-plugin-react18/tsconfig.json b/esbuild-plugin-react18/tsconfig.json similarity index 100% rename from packages/esbuild-plugin-react18/tsconfig.json rename to esbuild-plugin-react18/tsconfig.json diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 091d6e43..9257adbb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - "examples/*" - "packages/*" + - "esbuild-plugin-react18" From b5fd89b331a12e5f68c9736e99125fdcfbb5bb5d Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Wed, 13 Mar 2024 21:14:32 +0530 Subject: [PATCH 02/10] upgrade dependencies [skip ci] --- esbuild-plugin-react18/package.json | 12 ++++----- examples/nextjs/package.json | 14 +++++----- examples/vite/package.json | 22 ++++++++-------- package.json | 8 +++--- .../package.json | 26 +++++++++---------- packages/eslint-config-custom/package.json | 4 +-- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/esbuild-plugin-react18/package.json b/esbuild-plugin-react18/package.json index bb4b2497..986d9113 100644 --- a/esbuild-plugin-react18/package.json +++ b/esbuild-plugin-react18/package.json @@ -12,13 +12,13 @@ "publish-package": "cd dist && npm publish --provenance --access public" }, "devDependencies": { - "@types/node": "^20.8.3", - "@vitest/coverage-v8": "^0.34.6", - "esbuild": "^0.19.4", - "octokit": "^3.1.1", + "@types/node": "^20.11.27", + "@vitest/coverage-v8": "^1.3.1", + "esbuild": "^0.20.1", + "octokit": "^3.1.2", "tiny-glob": "^0.2.9", - "typescript": "^5.2.2", - "vitest": "^0.34.6" + "typescript": "^5.4.2", + "vitest": "^1.3.1" }, "repository": { "type": "git", diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 91eb0759..a6b562fb 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -10,19 +10,19 @@ }, "dependencies": { "esbuild-plugin-react18-example": "workspace:*", - "next": "^13.5.4", - "nextjs-themes": "^1.0.0", + "next": "^14.1.3", + "nextjs-themes": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { - "@next/eslint-plugin-next": "^13.5.4", - "@types/node": "^20.8.3", - "@types/react": "^18.2.25", - "@types/react-dom": "^18.2.11", + "@next/eslint-plugin-next": "^14.1.3", + "@types/node": "^20.11.27", + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.22", "esbuild-plugin-react18": "workspace:^", "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", - "typescript": "^5.2.2" + "typescript": "^5.4.2" } } diff --git a/examples/vite/package.json b/examples/vite/package.json index 8e812cc9..a6cbbc5a 100644 --- a/examples/vite/package.json +++ b/examples/vite/package.json @@ -10,23 +10,23 @@ "preview": "vite preview" }, "dependencies": { - "@mayank1513/fork-me": "^1.1.0", + "@mayank1513/fork-me": "^2.0.1", "esbuild-plugin-react18-example": "workspace:*", "react": "^18.2.0", "react-dom": "^18.2.0", - "react18-themes": "^1.0.8" + "react18-themes": "^3.1.0" }, "devDependencies": { - "@types/react": "^18.2.25", - "@types/react-dom": "^18.2.11", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", - "@vitejs/plugin-react-swc": "^3.4.0", + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.6.0", "esbuild-plugin-react18": "workspace:^", - "eslint": "^8.51.0", + "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "typescript": "^5.2.2", - "vite": "^4.4.11" + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.4.2", + "vite": "^5.1.6" } } diff --git a/package.json b/package.json index ab30a94a..af665ab5 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,md,css,scss}\"" }, "devDependencies": { - "@changesets/cli": "^2.26.2", - "eslint": "^8.51.0", - "prettier": "^3.0.3", + "@changesets/cli": "^2.27.1", + "eslint": "^8.57.0", + "prettier": "^3.2.5", "tsconfig": "workspace:*", - "turbo": "^1.10.15" + "turbo": "^1.12.5" }, "packageManager": "pnpm@8.6.10", "name": "esbuild-plugin-react18" diff --git a/packages/esbuild-plugin-react18-example/package.json b/packages/esbuild-plugin-react18-example/package.json index b76ab502..8e00d0a5 100644 --- a/packages/esbuild-plugin-react18-example/package.json +++ b/packages/esbuild-plugin-react18-example/package.json @@ -23,26 +23,26 @@ "lint": "eslint ." }, "devDependencies": { - "@testing-library/react": "^14.0.0", - "@turbo/gen": "^1.10.15", - "@types/node": "^20.8.3", - "@types/react": "^18.2.25", - "@types/react-dom": "^18.2.11", - "@vitejs/plugin-react": "^4.1.0", - "@vitest/coverage-v8": "^0.34.6", + "@testing-library/react": "^14.2.1", + "@turbo/gen": "^1.12.5", + "@types/node": "^20.11.27", + "@types/react": "^18.2.65", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "@vitest/coverage-v8": "^1.3.1", "esbuild-plugin-ignoretests": "^0.0.7", "esbuild-plugin-react18": "workspace:*", "esbuild-plugin-removetestid": "^0.0.5", "esbuild-react18-useclient": "^1.0.7", "eslint-config-custom": "workspace:*", - "jsdom": "^22.1.0", - "octokit": "^3.1.1", + "jsdom": "^24.0.0", + "octokit": "^3.1.2", "react": "^18.2.0", "tsconfig": "workspace:*", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vite-tsconfig-paths": "^4.2.1", - "vitest": "^0.34.6" + "tsup": "^8.0.2", + "typescript": "^5.4.2", + "vite-tsconfig-paths": "^4.3.1", + "vitest": "^1.3.1" }, "peerDependencies": { "@types/react": "16.8 - 18", diff --git a/packages/eslint-config-custom/package.json b/packages/eslint-config-custom/package.json index 1b9873e1..93030b04 100644 --- a/packages/eslint-config-custom/package.json +++ b/packages/eslint-config-custom/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "private": true, "devDependencies": { - "@vercel/style-guide": "^5.0.1", - "eslint-config-turbo": "^1.10.15" + "@vercel/style-guide": "^6.0.0", + "eslint-config-turbo": "^1.12.5" } } From 41df500e6b7a319671475db65127bc036d998a66 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 12:32:22 +0530 Subject: [PATCH 03/10] refactor --- .tkb | 35 ++++ .vscode/settings.json | 3 +- esbuild-plugin-react18/src/constants.ts | 8 + esbuild-plugin-react18/src/index.ts | 253 ++++++++++++------------ 4 files changed, 175 insertions(+), 124 deletions(-) create mode 100644 .tkb create mode 100644 esbuild-plugin-react18/src/constants.ts diff --git a/.tkb b/.tkb new file mode 100644 index 00000000..458ed68b --- /dev/null +++ b/.tkb @@ -0,0 +1,35 @@ +{ + "scope": "Workspace", + "tasks": { + "task-DLMsMCnSbDOg6Q_qs3XX4": { + "id": "task-DLMsMCnSbDOg6Q_qs3XX4", + "description": "Support Sass\n- CSSPrefix --- default - ''\n- ClassName -> CSSFileName__class", + "columnId": "column-todo" + }, + "QJCiT5pTHTGKydoayOOp8": { + "id": "QJCiT5pTHTGKydoayOOp8", + "description": "Breakdown into smaller functions", + "columnId": "column-todo" + } + }, + "columns": [ + { + "id": "column-todo", + "title": "To do", + "tasksIds": [ + "task-DLMsMCnSbDOg6Q_qs3XX4", + "QJCiT5pTHTGKydoayOOp8" + ] + }, + { + "id": "column-doing", + "title": "Doing", + "tasksIds": [] + }, + { + "id": "column-done", + "title": "Done", + "tasksIds": [] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f05f7e7..20d656cd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,5 +27,6 @@ "editor.wordWrap": "on", "editor.formatOnSave": true, "editor.formatOnPaste": true, - "editor.formatOnSaveMode": "file" + "editor.formatOnSaveMode": "file", + "mayank1513.trello-kanban.Workspace.filePath": ".tkb" } diff --git a/esbuild-plugin-react18/src/constants.ts b/esbuild-plugin-react18/src/constants.ts new file mode 100644 index 00000000..f8746bcc --- /dev/null +++ b/esbuild-plugin-react18/src/constants.ts @@ -0,0 +1,8 @@ +const uuid = () => (Date.now() * Math.random()).toString(36).slice(0, 8); + +/** regExp */ +export const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; +export const name = "esbuild-plugin-react18-" + uuid(); +export const ignoreNamespace = "mayank1513-ignore-" + uuid(); +export const keepNamespace = "mayank1513-keep-" + uuid(); +export const useClientRegExp = /['"]use client['"]\s?;/i; diff --git a/esbuild-plugin-react18/src/index.ts b/esbuild-plugin-react18/src/index.ts index 995513a2..4a9306dc 100644 --- a/esbuild-plugin-react18/src/index.ts +++ b/esbuild-plugin-react18/src/index.ts @@ -1,8 +1,19 @@ -import { OnLoadResult, Plugin } from "esbuild"; +import { BuildResult, OnLoadResult, Plugin, PluginBuild, build } from "esbuild"; import fs from "node:fs"; import path from "node:path"; +import { testPathRegExp, name, ignoreNamespace, keepNamespace, useClientRegExp } from "./constants"; -type React18PluginOptions = { +interface ignorePattern { + pathPattern: RegExp; + contentPatterns?: RegExp[]; +} + +interface ReplacePattern { + pathPattern: RegExp; + replaceParams: { pattern: RegExp; substitute: string }[]; +} + +interface React18PluginOptions { /** to not ignore tese files */ keepTests?: boolean; @@ -18,7 +29,7 @@ type React18PluginOptions = { * If contentPatterns are provided, only the files at matching paths * containing one or more of the content patterns will be ignored */ - ignorePatterns?: { pathPattern: RegExp; contentPatterns?: RegExp[] }[]; + ignorePatterns?: ignorePattern[]; /** * regExp patterns to find and replace in source files before build @@ -31,141 +42,137 @@ type React18PluginOptions = { * `sourceReplacePatterns` collide with these files, please set `keepTestIds` * to `true` and handle removing testsids yourself. */ - sourceReplacePatterns?: { - pathPattern: RegExp; - replaceParams: { pattern: RegExp; substitute: string }[]; - }[]; + sourceReplacePatterns?: ReplacePattern[]; /** * regExp patterns to find and replace in build files after build * Use with caution! Make sure same file do not match multiple patterns * to avoid any unexpected results. */ - buildReplacePatterns?: { - pathPattern: RegExp; - replaceParams: { pattern: RegExp; substitute: string }[]; - }[]; -}; - -/** 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 = {}) => ({ - 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; - build.initialOptions.write = false; - - if (!options.keepTests) { - build.onResolve({ filter: testPathRegExp }, args => ({ - path: args.path, - namespace: ignoreNamespace, - })); - if (!options.keepTestIds) { - /** remove data-testid */ - if (!options.sourceReplacePatterns) options.sourceReplacePatterns = []; - options.sourceReplacePatterns.push({ - pathPattern: /.*\.(j|t)s(x)?$/, - replaceParams: [{ pattern: /\s*data-testid="[^"]*"/gm, substitute: " " }], - }); - } - } - - 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); - 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: fullPath, namespace: keepNamespace }; - }); + buildReplacePatterns?: ReplacePattern[]; +} + +function removeTests(build: PluginBuild, options: React18PluginOptions) { + build.onResolve({ filter: testPathRegExp }, args => ({ + path: args.path, + namespace: ignoreNamespace, + })); + if (!options.keepTestIds) { + /** remove data-testid */ + if (!options.sourceReplacePatterns) options.sourceReplacePatterns = []; + options.sourceReplacePatterns.push({ + pathPattern: /.*\.(j|t)s(x)?$/, + replaceParams: [{ pattern: /\s*data-testid="[^"]*"/gm, substitute: " " }], }); - - 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"); - /** todo: test if loader is a valid OnLoadResult.loader - * If it is not a valid loader error will be thrown - */ - const loader = args.path.slice(args.path.lastIndexOf(".") + 1); - sourceReplacePattern.replaceParams.forEach(({ pattern, substitute }) => { - contents = contents.replace(pattern, substitute); - }); - return { contents, loader } as OnLoadResult; + } +} + +function ignoreFiles(ignorePattern: ignorePattern, build: PluginBuild) { + build.onResolve({ filter: ignorePattern.pathPattern }, args => { + /** remove content to avoid building/transpiling test files unnecessarily*/ + 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()) return { path: fullPath, namespace: keepNamespace }; + + const text = fs.readFileSync(fullPath, "utf8"); + for (const contentPattern of ignorePattern.contentPatterns) + if (contentPattern.test(text)) return { path: args.path, namespace: ignoreNamespace }; + + return { path: fullPath, namespace: keepNamespace }; + }); +} + +function replaceSource(sourceReplacePattern: ReplacePattern, build: PluginBuild) { + 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"); + /** todo: test if loader is a valid OnLoadResult.loader + * If it is not a valid loader error will be thrown + */ + const loader = args.path.slice(args.path.lastIndexOf(".") + 1); + sourceReplacePattern.replaceParams.forEach(({ pattern, substitute }) => { + contents = contents.replace(pattern, substitute); + }); + return { contents, loader } as OnLoadResult; + }); +} + +function replaceBuild(buildReplacePattern: ReplacePattern, result: BuildResult) { + result.outputFiles + ?.filter(f => buildReplacePattern.pathPattern.test(f.path)) + .forEach(f => { + let text = f.text; + buildReplacePattern.replaceParams.forEach(({ pattern, substitute }) => { + text = text.replace(pattern, substitute); }); + f.contents = new TextEncoder().encode(text); }); - - build.onLoad({ filter: /.*/, namespace: ignoreNamespace }, args => { - /** remove content to avoid building/transpiling test files unnecessarily*/ - return { contents: "" }; +} + +function onEndCallBack(result: BuildResult, options: React18PluginOptions, write?: boolean) { + /** fix use client */ + result.outputFiles + ?.filter(f => !f.path.endsWith(".map")) + .forEach(f => { + const txt = f.text; + if (txt.match(useClientRegExp)) { + const text = '"use client";\n' + txt.replace(useClientRegExp, ""); + f.contents = new TextEncoder().encode(text); + } }); - build.onLoad({ filter: /.*/, namespace: keepNamespace }, 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; - } + /** handle buildReplacePatterns */ + options.buildReplacePatterns?.forEach(replacePattern => replaceBuild(replacePattern, result)); + + /** Do not generate {empty} test files if keepTests is not set to true */ + if (!options.keepTests) { + result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); + } + + /** remove empty files */ + result.outputFiles = result.outputFiles?.filter(f => f.text.trim() !== ""); + /** 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); }); + } +} - const useClientRegExp = /['"]use client['"]\s?;/i; - - build.onEnd(result => { - result.outputFiles - ?.filter(f => !f.path.endsWith(".map")) - .forEach(f => { - const txt = f.text; - if (txt.match(useClientRegExp)) { - const text = '"use client";\n' + txt.replace(useClientRegExp, ""); - f.contents = new TextEncoder().encode(text); - } - }); - - /** handle buildReplacePatterns */ - options.buildReplacePatterns?.forEach(buildReplacePattern => { - result.outputFiles - ?.filter(f => buildReplacePattern.pathPattern.test(f.path)) - .forEach(f => { - let text = f.text; - buildReplacePattern.replaceParams.forEach(({ pattern, substitute }) => { - text = text.replace(pattern, substitute); - }); - f.contents = new TextEncoder().encode(text); - }); - }); +function setup(build: PluginBuild, options: React18PluginOptions = {}) { + const write = build.initialOptions.write; + build.initialOptions.write = false; - /** Do not generate {empty} test files if keepTests is not set to true */ - if (!options.keepTests) { - result.outputFiles = result.outputFiles?.filter(f => !testPathRegExp.test(f.path)); - } + if (!options.keepTests) removeTests(build, options); - /** remove empty files */ - result.outputFiles = result.outputFiles?.filter(f => f.text !== ""); - /** 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); - }); - } - }); - }, -}); + options.ignorePatterns?.forEach(ignorePattern => ignoreFiles(ignorePattern, build)); + + options.sourceReplacePatterns?.forEach(replacePattern => replaceSource(replacePattern, build)); + + build.onLoad({ filter: /.*/, namespace: ignoreNamespace }, () => { + /** remove content to avoid building/transpiling ignored files*/ + return { contents: "" }; + }); -const uuid = () => (Date.now() * Math.random()).toString(36).slice(0, 8); + build.onLoad({ filter: /.*/, namespace: keepNamespace }, 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; + } + }); + + build.onEnd(result => onEndCallBack(result, options, write)); +} + +/** 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 = {}) => ({ + name, + setup: build => setup(build, options), +}); export = react18Plugin; From ea36ef05ac69bbd414d1c2d0a7c1f9aa6793ed91 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 12:47:35 +0530 Subject: [PATCH 04/10] fix vitest config --- .tkb | 9 +++++---- esbuild-plugin-react18/vitest.config.ts | 13 +++++++++++++ .../esbuild-plugin-react18-example/vitest.config.ts | 1 - 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 esbuild-plugin-react18/vitest.config.ts diff --git a/.tkb b/.tkb index 458ed68b..0cab1953 100644 --- a/.tkb +++ b/.tkb @@ -9,7 +9,7 @@ "QJCiT5pTHTGKydoayOOp8": { "id": "QJCiT5pTHTGKydoayOOp8", "description": "Breakdown into smaller functions", - "columnId": "column-todo" + "columnId": "column-done" } }, "columns": [ @@ -17,8 +17,7 @@ "id": "column-todo", "title": "To do", "tasksIds": [ - "task-DLMsMCnSbDOg6Q_qs3XX4", - "QJCiT5pTHTGKydoayOOp8" + "task-DLMsMCnSbDOg6Q_qs3XX4" ] }, { @@ -29,7 +28,9 @@ { "id": "column-done", "title": "Done", - "tasksIds": [] + "tasksIds": [ + "QJCiT5pTHTGKydoayOOp8" + ] } ] } \ No newline at end of file diff --git a/esbuild-plugin-react18/vitest.config.ts b/esbuild-plugin-react18/vitest.config.ts new file mode 100644 index 00000000..78f6b204 --- /dev/null +++ b/esbuild-plugin-react18/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vitest/config"; + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + globals: true, + setupFiles: [], + coverage: { + include: ["src/**"], + reporter: ["text", "json", "html", "clover"], + }, + }, +}); diff --git a/packages/esbuild-plugin-react18-example/vitest.config.ts b/packages/esbuild-plugin-react18-example/vitest.config.ts index 7aebb70e..aa7b9494 100644 --- a/packages/esbuild-plugin-react18-example/vitest.config.ts +++ b/packages/esbuild-plugin-react18-example/vitest.config.ts @@ -12,6 +12,5 @@ export default defineConfig({ coverage: { reporter: ["text", "json", "html"], }, - threads: true, }, }); From 2d2efe81aad98897f6bc608e9495524c8da91772 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 12:47:45 +0530 Subject: [PATCH 05/10] update test workflow --- .github/workflows/test.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac7b872d..ca50c7d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,10 +11,10 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - run: npm i -g pnpm && pnpm i name: Install dependencies - run: pnpm build --filter esbuild-plugin-react18-example @@ -24,6 +24,13 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: - directory: ./packages/esbuild-plugin-react18 + directory: ./esbuild-plugin-react18 token: ${{ secrets.CODECOV_TOKEN }} flags: esbuild-plugin-react18 + + - uses: paambaati/codeclimate-action@v5.0.0 + continue-on-error: true + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + with: + coverageLocations: ./esbuild-plugin-react18/coverage/*.xml:clover From 95836d44d4f3a82aa15544d6b574daab200792e8 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 12:50:12 +0530 Subject: [PATCH 06/10] fix example tests --- .../src/client/star-me/star-me.test.tsx | 2 +- packages/esbuild-plugin-react18-example/vitest.config.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/esbuild-plugin-react18-example/src/client/star-me/star-me.test.tsx b/packages/esbuild-plugin-react18-example/src/client/star-me/star-me.test.tsx index 61623066..40e0c330 100644 --- a/packages/esbuild-plugin-react18-example/src/client/star-me/star-me.test.tsx +++ b/packages/esbuild-plugin-react18-example/src/client/star-me/star-me.test.tsx @@ -2,7 +2,7 @@ import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, describe, test, vi } from "vitest"; import { StarMe } from "./star-me"; -describe.concurrent("star-me", () => { +describe("star-me", () => { afterEach(cleanup); test("smoke", ({ expect }) => { diff --git a/packages/esbuild-plugin-react18-example/vitest.config.ts b/packages/esbuild-plugin-react18-example/vitest.config.ts index aa7b9494..c264278a 100644 --- a/packages/esbuild-plugin-react18-example/vitest.config.ts +++ b/packages/esbuild-plugin-react18-example/vitest.config.ts @@ -10,7 +10,9 @@ export default defineConfig({ globals: true, setupFiles: [], coverage: { - reporter: ["text", "json", "html"], + include: ["src/**"], + exclude: ["src/**/index.ts", "src/**/declaration.d.ts"], + reporter: ["text", "json", "clover", "html"], }, }, }); From 60515438d00e2bd411b85f75ed39c432f596ea2b Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 12:56:47 +0530 Subject: [PATCH 07/10] support use server --- esbuild-plugin-react18/src/constants.ts | 4 +++- esbuild-plugin-react18/src/index.ts | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/esbuild-plugin-react18/src/constants.ts b/esbuild-plugin-react18/src/constants.ts index f8746bcc..982d6e50 100644 --- a/esbuild-plugin-react18/src/constants.ts +++ b/esbuild-plugin-react18/src/constants.ts @@ -2,7 +2,9 @@ const uuid = () => (Date.now() * Math.random()).toString(36).slice(0, 8); /** regExp */ export const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; +export const useClientRegExp = /['"]use client['"]\s?;/i; +export const useServerRegExp = /['"]use server['"]\s?;/i; + export const name = "esbuild-plugin-react18-" + uuid(); export const ignoreNamespace = "mayank1513-ignore-" + uuid(); export const keepNamespace = "mayank1513-keep-" + uuid(); -export const useClientRegExp = /['"]use client['"]\s?;/i; diff --git a/esbuild-plugin-react18/src/index.ts b/esbuild-plugin-react18/src/index.ts index 4a9306dc..3e557193 100644 --- a/esbuild-plugin-react18/src/index.ts +++ b/esbuild-plugin-react18/src/index.ts @@ -1,7 +1,14 @@ -import { BuildResult, OnLoadResult, Plugin, PluginBuild, build } from "esbuild"; +import type { BuildResult, OnLoadResult, Plugin, PluginBuild } from "esbuild"; import fs from "node:fs"; import path from "node:path"; -import { testPathRegExp, name, ignoreNamespace, keepNamespace, useClientRegExp } from "./constants"; +import { + testPathRegExp, + name, + ignoreNamespace, + keepNamespace, + useClientRegExp, + useServerRegExp, +} from "./constants"; interface ignorePattern { pathPattern: RegExp; @@ -113,7 +120,7 @@ function replaceBuild(buildReplacePattern: ReplacePattern, result: BuildResult) } function onEndCallBack(result: BuildResult, options: React18PluginOptions, write?: boolean) { - /** fix use client */ + /** fix use client and use server*/ result.outputFiles ?.filter(f => !f.path.endsWith(".map")) .forEach(f => { @@ -122,6 +129,10 @@ function onEndCallBack(result: BuildResult, options: React18PluginOptions, write const text = '"use client";\n' + txt.replace(useClientRegExp, ""); f.contents = new TextEncoder().encode(text); } + if (txt.match(useServerRegExp)) { + const text = '"use server";\n' + txt.replace(useServerRegExp, ""); + f.contents = new TextEncoder().encode(text); + } }); /** handle buildReplacePatterns */ From 4453574dec2e0a7d68983d351be9200a476b69e5 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 13:17:00 +0530 Subject: [PATCH 08/10] handle module level use client and use server --- esbuild-plugin-react18/src/constants.ts | 2 -- esbuild-plugin-react18/src/index.ts | 31 +++++++++++-------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/esbuild-plugin-react18/src/constants.ts b/esbuild-plugin-react18/src/constants.ts index 982d6e50..66cabdbb 100644 --- a/esbuild-plugin-react18/src/constants.ts +++ b/esbuild-plugin-react18/src/constants.ts @@ -2,8 +2,6 @@ const uuid = () => (Date.now() * Math.random()).toString(36).slice(0, 8); /** regExp */ export const testPathRegExp = /.*\.(test|spec|check)\.(j|t)s(x)?$/i; -export const useClientRegExp = /['"]use client['"]\s?;/i; -export const useServerRegExp = /['"]use server['"]\s?;/i; export const name = "esbuild-plugin-react18-" + uuid(); export const ignoreNamespace = "mayank1513-ignore-" + uuid(); diff --git a/esbuild-plugin-react18/src/index.ts b/esbuild-plugin-react18/src/index.ts index 3e557193..d7b525be 100644 --- a/esbuild-plugin-react18/src/index.ts +++ b/esbuild-plugin-react18/src/index.ts @@ -1,14 +1,7 @@ import type { BuildResult, OnLoadResult, Plugin, PluginBuild } from "esbuild"; import fs from "node:fs"; import path from "node:path"; -import { - testPathRegExp, - name, - ignoreNamespace, - keepNamespace, - useClientRegExp, - useServerRegExp, -} from "./constants"; +import { testPathRegExp, name, ignoreNamespace, keepNamespace } from "./constants"; interface ignorePattern { pathPattern: RegExp; @@ -124,15 +117,19 @@ function onEndCallBack(result: BuildResult, options: React18PluginOptions, write result.outputFiles ?.filter(f => !f.path.endsWith(".map")) .forEach(f => { - const txt = f.text; - if (txt.match(useClientRegExp)) { - const text = '"use client";\n' + txt.replace(useClientRegExp, ""); - f.contents = new TextEncoder().encode(text); - } - if (txt.match(useServerRegExp)) { - const text = '"use server";\n' + txt.replace(useServerRegExp, ""); - f.contents = new TextEncoder().encode(text); - } + let txt = f.text; + txt = txt.replace( + /^(["']use strict["'];)?["']use client["'];?/i, + '"use client";\n"use strict";', + ); + + /** module level use server */ + txt = txt.replace( + /^(["']use strict["'];)?["']use server["'];?/i, + '"use server";\n"use strict";', + ); + + f.contents = new TextEncoder().encode(txt); }); /** handle buildReplacePatterns */ From e3d0231f2c1a94e29d1b1e0c5725504beb79d5a7 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 13:20:55 +0530 Subject: [PATCH 09/10] Apply changeset --- esbuild-plugin-react18/CHANGELOG.md | 6 ++++++ esbuild-plugin-react18/package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/esbuild-plugin-react18/CHANGELOG.md b/esbuild-plugin-react18/CHANGELOG.md index 4b7992ce..da8fe14e 100644 --- a/esbuild-plugin-react18/CHANGELOG.md +++ b/esbuild-plugin-react18/CHANGELOG.md @@ -1,5 +1,11 @@ # esbuild-plugin-react18 +## 0.1.0 + +### Minor Changes + +- Support module level "use server" directive + ## 0.0.7 ### Patch Changes diff --git a/esbuild-plugin-react18/package.json b/esbuild-plugin-react18/package.json index 986d9113..b3866866 100644 --- a/esbuild-plugin-react18/package.json +++ b/esbuild-plugin-react18/package.json @@ -2,7 +2,7 @@ "name": "esbuild-plugin-react18", "author": "Mayank Kumar Chaudhari ", "private": false, - "version": "0.0.7", + "version": "0.1.0", "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", From 6f1239f79a1fc4a16c76f90ac98dc7ebda382b46 Mon Sep 17 00:00:00 2001 From: Mayank Kumar Chaudhari Date: Fri, 15 Mar 2024 13:29:10 +0530 Subject: [PATCH 10/10] update publish workflow --- .github/workflows/publish.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b3107f98..2c490409 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,7 +4,7 @@ name: Publish to NPM on: push: branches: [main] - paths: "packages/esbuild-plugin-react18/package.json" + paths: "esbuild-plugin-react18/package.json" jobs: publish: @@ -19,16 +19,16 @@ jobs: defaults: run: - working-directory: ./packages/esbuild-plugin-react18 + working-directory: ./esbuild-plugin-react18 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 registry-url: https://registry.npmjs.org - run: npm i -g pnpm && pnpm i name: Install dependencies @@ -58,3 +58,9 @@ jobs: TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER: ${{ github.event.repository.owner.login }} REPO: ${{ github.event.repository.name }} + + - name: Mark scoped package as deprecated + run: | + npm deprecate @mayank1513/esbuild-plugin-react18 "Please use https://www.npmjs.com/package/esbuild-plugin-react18 instead. We initially created scoped packages to have similarities with the GitHub Public Repository (which requires packages to be scoped). We are no longer using GPR and thus deprecating all scoped packages for which corresponding un-scoped packages exist." + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}