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 }} 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 diff --git a/.tkb b/.tkb new file mode 100644 index 00000000..0cab1953 --- /dev/null +++ b/.tkb @@ -0,0 +1,36 @@ +{ + "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-done" + } + }, + "columns": [ + { + "id": "column-todo", + "title": "To do", + "tasksIds": [ + "task-DLMsMCnSbDOg6Q_qs3XX4" + ] + }, + { + "id": "column-doing", + "title": "Doing", + "tasksIds": [] + }, + { + "id": "column-done", + "title": "Done", + "tasksIds": [ + "QJCiT5pTHTGKydoayOOp8" + ] + } + ] +} \ 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/packages/esbuild-plugin-react18/CHANGELOG.md b/esbuild-plugin-react18/CHANGELOG.md similarity index 90% rename from packages/esbuild-plugin-react18/CHANGELOG.md rename to esbuild-plugin-react18/CHANGELOG.md index 4b7992ce..da8fe14e 100644 --- a/packages/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/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 88% rename from packages/esbuild-plugin-react18/package.json rename to esbuild-plugin-react18/package.json index bb4b2497..b3866866 100644 --- a/packages/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", @@ -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/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/esbuild-plugin-react18/src/constants.ts b/esbuild-plugin-react18/src/constants.ts new file mode 100644 index 00000000..66cabdbb --- /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(); diff --git a/esbuild-plugin-react18/src/index.ts b/esbuild-plugin-react18/src/index.ts new file mode 100644 index 00000000..d7b525be --- /dev/null +++ b/esbuild-plugin-react18/src/index.ts @@ -0,0 +1,186 @@ +import type { BuildResult, OnLoadResult, Plugin, PluginBuild } from "esbuild"; +import fs from "node:fs"; +import path from "node:path"; +import { testPathRegExp, name, ignoreNamespace, keepNamespace } from "./constants"; + +interface ignorePattern { + pathPattern: RegExp; + contentPatterns?: RegExp[]; +} + +interface ReplacePattern { + pathPattern: RegExp; + replaceParams: { pattern: RegExp; substitute: string }[]; +} + +interface React18PluginOptions { + /** to not ignore tese files */ + keepTests?: boolean; + + /** 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` + */ + keepTestIds?: boolean; + + /** + * regExp patterns to match file paths to be ignored. + * If contentPatterns are provided, only the files at matching paths + * containing one or more of the content patterns will be ignored + */ + ignorePatterns?: ignorePattern[]; + + /** + * regExp patterns to find and replace in source files before build + * + * Use with caution! Make sure same file do not match multiple patterns + * to avoid any unexpected results. + * + * Caution! - if you have not enabled `keepTests`, we are already using + * `/.*\.(j|t)s(x)?$/` pattern to remove `data-testid` attributes. If your + * `sourceReplacePatterns` collide with these files, please set `keepTestIds` + * to `true` and handle removing testsids yourself. + */ + 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?: 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: " " }], + }); + } +} + +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); + }); +} + +function onEndCallBack(result: BuildResult, options: React18PluginOptions, write?: boolean) { + /** fix use client and use server*/ + result.outputFiles + ?.filter(f => !f.path.endsWith(".map")) + .forEach(f => { + 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 */ + 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); + }); + } +} + +function setup(build: PluginBuild, options: React18PluginOptions = {}) { + const write = build.initialOptions.write; + build.initialOptions.write = false; + + if (!options.keepTests) removeTests(build, options); + + 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: "" }; + }); + + 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; 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/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/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/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 7aebb70e..c264278a 100644 --- a/packages/esbuild-plugin-react18-example/vitest.config.ts +++ b/packages/esbuild-plugin-react18-example/vitest.config.ts @@ -10,8 +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"], }, - threads: true, }, }); diff --git a/packages/esbuild-plugin-react18/src/index.ts b/packages/esbuild-plugin-react18/src/index.ts deleted file mode 100644 index 995513a2..00000000 --- a/packages/esbuild-plugin-react18/src/index.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { OnLoadResult, Plugin } from "esbuild"; -import fs from "node:fs"; -import path from "node:path"; - -type React18PluginOptions = { - /** to not ignore tese files */ - keepTests?: boolean; - - /** 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` - */ - keepTestIds?: boolean; - - /** - * regExp patterns to match file paths to be ignored. - * 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[] }[]; - - /** - * regExp patterns to find and replace in source files before build - * - * Use with caution! Make sure same file do not match multiple patterns - * to avoid any unexpected results. - * - * Caution! - if you have not enabled `keepTests`, we are already using - * `/.*\.(j|t)s(x)?$/` pattern to remove `data-testid` attributes. If your - * `sourceReplacePatterns` collide with these files, please set `keepTestIds` - * to `true` and handle removing testsids yourself. - */ - sourceReplacePatterns?: { - pathPattern: RegExp; - replaceParams: { pattern: RegExp; substitute: string }[]; - }[]; - - /** - * 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 }; - }); - }); - - 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; - }); - }); - - build.onLoad({ filter: /.*/, namespace: ignoreNamespace }, args => { - /** remove content to avoid building/transpiling test files unnecessarily*/ - return { contents: "" }; - }); - - 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; - } - }); - - 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); - }); - }); - - /** 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 !== ""); - /** 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 uuid = () => (Date.now() * Math.random()).toString(36).slice(0, 8); - -export = react18Plugin; 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" } } 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"