├── .commitlintrc.json
├── .github
├── FUNDING.yml
└── workflows
│ ├── check-patchable.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .husky
├── commit-msg
├── pre-commit
└── pre-push
├── .releaserc.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cron
├── .gitignore
├── app.jsx
├── index.js
├── inject.js
├── package.json
└── yarn.lock
├── examples
├── chakra-ui-cloudflare
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── context.tsx
│ │ ├── createEmotionCache.ts
│ │ ├── entry.client.tsx
│ │ ├── entry.server.tsx
│ │ ├── root.tsx
│ │ └── routes
│ │ │ └── index.tsx
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── remix.config.js
│ ├── remix.env.d.ts
│ ├── server.js
│ ├── tsconfig.json
│ └── wrangler.toml
├── emotion-cloudflare
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ │ ├── entry.client.tsx
│ │ ├── entry.server.tsx
│ │ ├── root.tsx
│ │ ├── routes
│ │ │ ├── index.tsx
│ │ │ ├── jokes-error.tsx
│ │ │ ├── jokes.tsx
│ │ │ └── use-css-props.tsx
│ │ └── styles
│ │ │ ├── client.context.tsx
│ │ │ ├── createEmotionCache.ts
│ │ │ └── server.context.tsx
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── favicon.ico
│ ├── remix.config.js
│ ├── remix.env.d.ts
│ ├── server.js
│ ├── tsconfig.json
│ └── wrangler.toml
└── styled-components
│ ├── .eslintrc
│ ├── .gitignore
│ ├── README.md
│ ├── app
│ ├── entry.client.tsx
│ ├── entry.server.tsx
│ ├── root.tsx
│ └── routes
│ │ └── index.tsx
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ └── favicon.ico
│ ├── remix.config.js
│ ├── remix.env.d.ts
│ ├── styled-components-esbuild-plugin.js
│ └── tsconfig.json
├── package.json
├── src
├── __tests__
│ ├── __snapshots__
│ │ └── patching.test.ts.snap
│ ├── index.test.ts
│ └── patching.test.ts
├── bin
│ └── esbuild-override.ts
├── constants.ts
├── index.ts
├── patching.ts
└── utils.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.commitlintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@commitlint/config-conventional"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: aiji42 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/workflows/check-patchable.yml:
--------------------------------------------------------------------------------
1 | name: check-patchable
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 | schedule:
11 | - cron: '0 10 * * *'
12 |
13 | jobs:
14 | test:
15 | name: Test
16 | runs-on: ubuntu-latest
17 | steps:
18 | - name: Checkout Repository
19 | uses: actions/checkout@master
20 | - name: Install Dependencies
21 | working-directory: ./cron
22 | run: |
23 | yarn install --no-lockfile
24 | yarn list esbuild remix-esbuild-override
25 | - name: Run Test
26 | working-directory: ./cron
27 | run: yarn test
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 | - beta
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout this repository
12 | uses: actions/checkout@master
13 | - name: Setup node
14 | uses: actions/setup-node@v3
15 | with:
16 | node-version: '16'
17 | - name: Install dependencies
18 | run: yarn --frozen-lockfile
19 | - name: Build
20 | run: yarn build
21 | - name: Release
22 | env:
23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
25 | run: yarn semantic-release
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 | jobs:
10 | test:
11 | name: Test
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout Repository
15 | uses: actions/checkout@master
16 | - name: Install Dependencies
17 | run: yarn
18 | - name: Run Test
19 | run: yarn test:coverage
20 | - name: Upload Coverage
21 | uses: codecov/codecov-action@v1
22 | with:
23 | token: ${{ secrets.CODECOV_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules/
3 | *.log
4 | dist
5 | coverage
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn commitlint --edit
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run test:run
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "branches": ["main", {"name": "beta", "prerelease": true}]
3 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributions
2 |
3 | :crystal_ball: Thanks for considering contributing to this project! :crystal_ball:
4 |
5 | These guidelines will help you send a pull request.
6 |
7 | If you're submitting an issue instead, please skip this document.
8 |
9 | If your pull request is related to a typo or the documentation being unclear, please click on the relevant page's `Edit`
10 | button (pencil icon) and directly suggest a correction instead.
11 |
12 | This project was made with your goodwill. The simplest way to give back is by starring and sharing it online.
13 |
14 | Everyone is welcome regardless of personal background.
15 |
16 | ## Development process
17 |
18 | First fork and clone the repository.
19 |
20 | Run:
21 |
22 | ```bash
23 | yarn
24 | ```
25 |
26 | Make sure everything is correctly setup with:
27 |
28 | ```bash
29 | yarn test:run
30 | ```
31 |
32 | ## How to write commit messages
33 |
34 | We use [Conventional Commit messages](https://www.conventionalcommits.org/) to automate version management.
35 |
36 | Most common commit message prefixes are:
37 |
38 | * `fix:` which represents bug fixes, and generate a patch release.
39 | * `feat:` which represents a new feature, and generate a minor release.
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 AijiUejima
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/remix-esbuild-override)
2 | [](https://codecov.io/gh/aiji42/remix-esbuild-override)
3 |
4 | # :minidisc: remix-esbuild-override
5 |
6 | :warning: While I believe you will most likely get a lot of benefit from using this library, it can sometimes destroy your product.
7 | Please be sure to verify it and make sure it is safe before releasing it to production.
8 |
9 | ## What is this?
10 |
11 | This is a library that makes it possible to change the configuration values of the [Remix](https://remix.run/) compiler (esbuild).
12 |
13 | For example, Next.js allows you to control webpack option values from a configuration file (`next.config.js`).
14 | Remix does not have that functionality. A member of the development team says in a [PR comment](https://github.com/remix-run/remix/pull/2168#issuecomment-1058193715) that this is because exposing the configuration values would lock in the compiler's choices and also risk breaking the application.
15 | I support that argument, but in actual use cases, I often want to change the settings.
16 | So I decided to provide that functionality outside of Remix (in this 3rd-party library).
17 |
18 | ## Install
19 |
20 | ```bash
21 | # npm
22 | npm install -D remix-esbuild-override
23 |
24 | # yarn
25 | yarn add -D remix-esbuild-override
26 | ```
27 |
28 | 2. Add `remix-esbuild-override` to `scripts.postinstall` in package.json.
29 |
30 | ```json
31 | "scripts": {
32 | "postinstall": "remix-esbuild-override"
33 | }
34 | ```
35 |
36 | 3. Run `npm install` or `yarn install` again to run `postinstall`
37 |
38 | ## How to use
39 |
40 | You can define function properties in `remix.config.js` that can override esbuild configuration values.
41 |
42 | ```js
43 | // remix.config.js
44 | const { withEsbuildOverride } = require("remix-esbuild-override");
45 |
46 | /**
47 | * Define callbacks for the arguments of withEsbuildOverride.
48 | * @param option - Default configuration values defined by the remix compiler
49 | * @param isServer - True for server compilation, false for browser compilation
50 | * @param isDev - True during development.
51 | * @return {EsbuildOption} - You must return the updated option
52 | */
53 | withEsbuildOverride((option, { isServer, isDev }) => {
54 | // update the option
55 | option.plugins = [someEsbuildPlugin, ...option.plugins];
56 |
57 | return option;
58 | });
59 |
60 | /**
61 | * @type {import('@remix-run/dev').AppConfig}
62 | */
63 | module.exports = {
64 | // ...
65 | };
66 | ```
67 |
68 | :memo: NOTE: Compilation is executed twice, once for the server and once for the browser.
69 |
70 | ### Examples
71 |
72 | - [emotion on Cloudflare](https://github.com/aiji42/remix-esbuild-override/tree/main/examples/emotion-cloudflare)
73 | - [Chakra UI on Cloudflare](https://github.com/aiji42/remix-esbuild-override/tree/main/examples/chakra-ui-cloudflare)
74 | - [Styled componets](https://github.com/aiji42/remix-esbuild-override/tree/main/examples/styled-components)
75 |
76 | If you have other example requests, please create an issue. Additional pull requests for examples are also welcome.
77 |
78 | ## Contributing
79 |
80 | Please read [CONTRIBUTING.md](https://github.com/aiji42/remix-esbuild-override/blob/main/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
81 |
82 | ## License
83 |
84 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/aiji42/remix-esbuild-override/blob/main/LICENSE) file for details
85 |
--------------------------------------------------------------------------------
/cron/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | out.js
--------------------------------------------------------------------------------
/cron/app.jsx:
--------------------------------------------------------------------------------
1 | import { renderToString } from "react-dom/server";
2 |
3 | const Greet = () =>
Hello, world!
;
4 | console.log(renderToString());
5 |
--------------------------------------------------------------------------------
/cron/index.js:
--------------------------------------------------------------------------------
1 | const { withEsbuildOverride } = require("remix-esbuild-override");
2 | const path = require("path");
3 |
4 | withEsbuildOverride((option) => {
5 | option.inject = [path.resolve(__dirname, "inject.js")];
6 | return option;
7 | });
8 |
9 | const main = async () => {
10 | await require("esbuild").build({
11 | entryPoints: ["app.jsx"],
12 | bundle: true,
13 | minify: true,
14 | outfile: "out.js",
15 | });
16 |
17 | const res = await require("esbuild").context({
18 | entryPoints: ["app.jsx"],
19 | bundle: true,
20 | minify: true,
21 | outfile: "out.js",
22 | });
23 | await res.dispose();
24 | };
25 |
26 | main().catch(() => process.exit(1));
27 |
--------------------------------------------------------------------------------
/cron/inject.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | export { React };
3 |
--------------------------------------------------------------------------------
/cron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cron",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "postinstall": "remix-esbuild-override",
8 | "test": "node index.js && node out.js"
9 | },
10 | "dependencies": {
11 | "esbuild": "latest",
12 | "react": "^18.0.0",
13 | "react-dom": "^18.0.0",
14 | "remix-esbuild-override": "latest"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/cron/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/android-arm64@0.17.18":
6 | version "0.17.18"
7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.18.tgz#4aa8d8afcffb4458736ca9b32baa97d7cb5861ea"
8 | integrity sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==
9 |
10 | "@esbuild/android-arm@0.17.18":
11 | version "0.17.18"
12 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.18.tgz#74a7e95af4ee212ebc9db9baa87c06a594f2a427"
13 | integrity sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==
14 |
15 | "@esbuild/android-x64@0.17.18":
16 | version "0.17.18"
17 | resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.18.tgz#1dcd13f201997c9fe0b204189d3a0da4eb4eb9b6"
18 | integrity sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==
19 |
20 | "@esbuild/darwin-arm64@0.17.18":
21 | version "0.17.18"
22 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.18.tgz#444f3b961d4da7a89eb9bd35cfa4415141537c2a"
23 | integrity sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==
24 |
25 | "@esbuild/darwin-x64@0.17.18":
26 | version "0.17.18"
27 | resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.18.tgz#a6da308d0ac8a498c54d62e0b2bfb7119b22d315"
28 | integrity sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==
29 |
30 | "@esbuild/freebsd-arm64@0.17.18":
31 | version "0.17.18"
32 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.18.tgz#b83122bb468889399d0d63475d5aea8d6829c2c2"
33 | integrity sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==
34 |
35 | "@esbuild/freebsd-x64@0.17.18":
36 | version "0.17.18"
37 | resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.18.tgz#af59e0e03fcf7f221b34d4c5ab14094862c9c864"
38 | integrity sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==
39 |
40 | "@esbuild/linux-arm64@0.17.18":
41 | version "0.17.18"
42 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.18.tgz#8551d72ba540c5bce4bab274a81c14ed01eafdcf"
43 | integrity sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==
44 |
45 | "@esbuild/linux-arm@0.17.18":
46 | version "0.17.18"
47 | resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.18.tgz#e09e76e526df4f665d4d2720d28ff87d15cdf639"
48 | integrity sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==
49 |
50 | "@esbuild/linux-ia32@0.17.18":
51 | version "0.17.18"
52 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.18.tgz#47878860ce4fe73a36fd8627f5647bcbbef38ba4"
53 | integrity sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==
54 |
55 | "@esbuild/linux-loong64@0.17.18":
56 | version "0.17.18"
57 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.18.tgz#3f8fbf5267556fc387d20b2e708ce115de5c967a"
58 | integrity sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==
59 |
60 | "@esbuild/linux-mips64el@0.17.18":
61 | version "0.17.18"
62 | resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.18.tgz#9d896d8f3c75f6c226cbeb840127462e37738226"
63 | integrity sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==
64 |
65 | "@esbuild/linux-ppc64@0.17.18":
66 | version "0.17.18"
67 | resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.18.tgz#3d9deb60b2d32c9985bdc3e3be090d30b7472783"
68 | integrity sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==
69 |
70 | "@esbuild/linux-riscv64@0.17.18":
71 | version "0.17.18"
72 | resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.18.tgz#8a943cf13fd24ff7ed58aefb940ef178f93386bc"
73 | integrity sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==
74 |
75 | "@esbuild/linux-s390x@0.17.18":
76 | version "0.17.18"
77 | resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.18.tgz#66cb01f4a06423e5496facabdce4f7cae7cb80e5"
78 | integrity sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==
79 |
80 | "@esbuild/linux-x64@0.17.18":
81 | version "0.17.18"
82 | resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.18.tgz#23c26050c6c5d1359c7b774823adc32b3883b6c9"
83 | integrity sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==
84 |
85 | "@esbuild/netbsd-x64@0.17.18":
86 | version "0.17.18"
87 | resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.18.tgz#789a203d3115a52633ff6504f8cbf757f15e703b"
88 | integrity sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==
89 |
90 | "@esbuild/openbsd-x64@0.17.18":
91 | version "0.17.18"
92 | resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.18.tgz#d7b998a30878f8da40617a10af423f56f12a5e90"
93 | integrity sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==
94 |
95 | "@esbuild/sunos-x64@0.17.18":
96 | version "0.17.18"
97 | resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.18.tgz#ecad0736aa7dae07901ba273db9ef3d3e93df31f"
98 | integrity sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==
99 |
100 | "@esbuild/win32-arm64@0.17.18":
101 | version "0.17.18"
102 | resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.18.tgz#58dfc177da30acf956252d7c8ae9e54e424887c4"
103 | integrity sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==
104 |
105 | "@esbuild/win32-ia32@0.17.18":
106 | version "0.17.18"
107 | resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.18.tgz#340f6163172b5272b5ae60ec12c312485f69232b"
108 | integrity sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==
109 |
110 | "@esbuild/win32-x64@0.17.18":
111 | version "0.17.18"
112 | resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.18.tgz#3a8e57153905308db357fd02f57c180ee3a0a1fa"
113 | integrity sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==
114 |
115 | esbuild@0.17.18:
116 | version "0.17.18"
117 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.18.tgz#f4f8eb6d77384d68cd71c53eb6601c7efe05e746"
118 | integrity sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==
119 | optionalDependencies:
120 | "@esbuild/android-arm" "0.17.18"
121 | "@esbuild/android-arm64" "0.17.18"
122 | "@esbuild/android-x64" "0.17.18"
123 | "@esbuild/darwin-arm64" "0.17.18"
124 | "@esbuild/darwin-x64" "0.17.18"
125 | "@esbuild/freebsd-arm64" "0.17.18"
126 | "@esbuild/freebsd-x64" "0.17.18"
127 | "@esbuild/linux-arm" "0.17.18"
128 | "@esbuild/linux-arm64" "0.17.18"
129 | "@esbuild/linux-ia32" "0.17.18"
130 | "@esbuild/linux-loong64" "0.17.18"
131 | "@esbuild/linux-mips64el" "0.17.18"
132 | "@esbuild/linux-ppc64" "0.17.18"
133 | "@esbuild/linux-riscv64" "0.17.18"
134 | "@esbuild/linux-s390x" "0.17.18"
135 | "@esbuild/linux-x64" "0.17.18"
136 | "@esbuild/netbsd-x64" "0.17.18"
137 | "@esbuild/openbsd-x64" "0.17.18"
138 | "@esbuild/sunos-x64" "0.17.18"
139 | "@esbuild/win32-arm64" "0.17.18"
140 | "@esbuild/win32-ia32" "0.17.18"
141 | "@esbuild/win32-x64" "0.17.18"
142 |
143 | "js-tokens@^3.0.0 || ^4.0.0":
144 | version "4.0.0"
145 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
146 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
147 |
148 | loose-envify@^1.1.0:
149 | version "1.4.0"
150 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
151 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
152 | dependencies:
153 | js-tokens "^3.0.0 || ^4.0.0"
154 |
155 | react-dom@^18.2.0:
156 | version "18.2.0"
157 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
158 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
159 | dependencies:
160 | loose-envify "^1.1.0"
161 | scheduler "^0.23.0"
162 |
163 | react@^18.2.0:
164 | version "18.2.0"
165 | resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
166 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
167 | dependencies:
168 | loose-envify "^1.1.0"
169 |
170 | remix-esbuild-override@3.1.0:
171 | version "3.1.0"
172 | resolved "https://registry.yarnpkg.com/remix-esbuild-override/-/remix-esbuild-override-3.1.0.tgz#9f586dd4ded58bdc48920b8a855a6596ce8d2f56"
173 | integrity sha512-K9eIukm1n+K0d2ZPEbRYoj/NHWp3rsa0R9YO/U3bNDnjCVNItJBLSIS5sNqoELLb5kdnbXP2062gJ1yWpjMHrQ==
174 |
175 | scheduler@^0.23.0:
176 | version "0.23.0"
177 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
178 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
179 | dependencies:
180 | loose-envify "^1.1.0"
181 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('eslint').Linter.Config} */
2 | module.exports = {
3 | extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
4 | };
5 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /build
5 | /dist
6 | /public/build
7 | /.mf
8 | .env
9 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/README.md:
--------------------------------------------------------------------------------
1 | # Remix with [Chakra UI](https://chakra-ui.com/) on Cloudflare
2 |
3 | The base template is based on the official [setup document](https://chakra-ui.com/guides/getting-started/remix-guide) and is exactly as it is under the app directory.
4 | This example is for Cloudflare Workers, but it is basically the same for Cloudflare Pages.
5 |
6 | Since @emotion/server depends on Buffer, it cannot run on Cloudflare as is.
7 |
8 | ## Use remix-esbuild-override
9 |
10 | Use remix-esbuild-override and the plugin for polyfill to solve the problem.
11 |
12 | 1. Install and setup `postinstall`
13 |
14 | ```bash
15 | npm install -D remix-esbuild-override @esbuild-plugins/node-globals-polyfill
16 | # or
17 | yarn add -D remix-esbuild-override @esbuild-plugins/node-globals-polyfill
18 | ```
19 |
20 | Update `scripts > postinstall` in package.json.
21 |
22 | ```json
23 | "scripts": {
24 | "postinstall": "remix-esbuild-override"
25 | }
26 | ```
27 |
28 | **Run `npm install` or `yarn install` again to run `postinstall`.**
29 |
30 | 2. Update remix.config.js
31 |
32 | ```js
33 | const { withEsbuildOverride } = require("remix-esbuild-override");
34 | const GlobalsPolyfills =
35 | require("@esbuild-plugins/node-globals-polyfill").default;
36 |
37 | withEsbuildOverride((option, { isServer }) => {
38 | if (isServer)
39 | option.plugins = [
40 | GlobalsPolyfills({
41 | buffer: true,
42 | }),
43 | ...option.plugins,
44 | ];
45 |
46 | return option;
47 | });
48 |
49 | /** @type {import('@remix-run/dev').AppConfig} */
50 | module.exports = {
51 | serverBuildTarget: "cloudflare-workers",
52 | server: "./server.js",
53 | devServerBroadcastDelay: 1000,
54 | ignoredRouteFiles: ["**/.*"],
55 | // appDirectory: "app",
56 | // assetsBuildDirectory: "public/build",
57 | // serverBuildPath: "build/index.js",
58 | // publicPath: "/build/",
59 | };
60 | ```
61 |
62 | That's all.
63 |
64 | ---
65 |
66 | # Welcome to Remix!
67 |
68 | - [Remix Docs](https://remix.run/docs)
69 |
70 | ## Development
71 |
72 | You will be running two processes during development:
73 |
74 | - The Miniflare server (miniflare is a local environment for Cloudflare Workers)
75 | - The Remix development server
76 |
77 | Both are started with one command:
78 |
79 | ```sh
80 | npm run dev
81 | ```
82 |
83 | Open up [http://127.0.0.1:8787](http://127.0.0.1:8787) and you should be ready to go!
84 |
85 | If you want to check the production build, you can stop the dev server and run following commands:
86 |
87 | ```sh
88 | npm run build
89 | npm start
90 | ```
91 |
92 | Then refresh the same URL in your browser (no live reload for production builds).
93 |
94 | ## Deployment
95 |
96 | If you don't already have an account, then [create a cloudflare account here](https://dash.cloudflare.com/sign-up) and after verifying your email address with Cloudflare, go to your dashboard and set up your free custom Cloudflare Workers subdomain.
97 |
98 | Once that's done, you should be able to deploy your app:
99 |
100 | ```sh
101 | npm run deploy
102 | ```
103 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/context.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext } from 'react'
2 |
3 | export interface ServerStyleContextData {
4 | key: string
5 | ids: Array
6 | css: string
7 | }
8 |
9 | export const ServerStyleContext = createContext(null)
10 |
11 | export interface ClientStyleContextData {
12 | reset: () => void
13 | }
14 |
15 | export const ClientStyleContext = createContext(null)
16 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | export const defaultCache = createEmotionCache();
4 |
5 | export default function createEmotionCache() {
6 | return createCache({ key: "cha" });
7 | }
8 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | import { RemixBrowser } from "@remix-run/react";
2 | import { ReactNode, startTransition, StrictMode, useState } from "react";
3 | import { hydrateRoot } from "react-dom/client";
4 | import { ClientStyleContext } from "./context";
5 | import createEmotionCache, { defaultCache } from "./createEmotionCache";
6 | import { CacheProvider } from "@emotion/react";
7 |
8 | interface ClientCacheProviderProps {
9 | children: ReactNode;
10 | }
11 |
12 | function ClientCacheProvider({ children }: ClientCacheProviderProps) {
13 | const [cache, setCache] = useState(defaultCache);
14 |
15 | function reset() {
16 | setCache(createEmotionCache());
17 | }
18 |
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | }
25 |
26 | function hydrate() {
27 | startTransition(() => {
28 | hydrateRoot(
29 | document,
30 |
31 |
32 |
33 |
34 |
35 | );
36 | });
37 | }
38 |
39 | if (window.requestIdleCallback) {
40 | window.requestIdleCallback(hydrate);
41 | } else {
42 | // Safari doesn't support requestIdleCallback
43 | // https://caniuse.com/requestidlecallback
44 | window.setTimeout(hydrate, 1);
45 | }
46 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/entry.server.tsx:
--------------------------------------------------------------------------------
1 | import type { EntryContext } from "@remix-run/cloudflare";
2 | import { RemixServer } from "@remix-run/react";
3 | import { renderToString } from "react-dom/server";
4 | import createEmotionCache from "~/createEmotionCache";
5 | import createEmotionServer from "@emotion/server/create-instance";
6 | import { ServerStyleContext } from "~/context";
7 | import { CacheProvider } from "@emotion/react";
8 |
9 | export default function handleRequest(
10 | request: Request,
11 | responseStatusCode: number,
12 | responseHeaders: Headers,
13 | remixContext: EntryContext
14 | ) {
15 | const cache = createEmotionCache()
16 | const { extractCriticalToChunks } = createEmotionServer(cache)
17 |
18 | const html = renderToString(
19 |
20 |
21 |
22 |
23 | ,
24 | )
25 |
26 | const chunks = extractCriticalToChunks(html)
27 |
28 | const markup = renderToString(
29 |
30 |
31 |
32 |
33 | ,
34 | )
35 |
36 | responseHeaders.set('Content-Type', 'text/html')
37 |
38 | return new Response(`${markup}`, {
39 | status: responseStatusCode,
40 | headers: responseHeaders,
41 | })
42 | }
43 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/root.tsx:
--------------------------------------------------------------------------------
1 | import type { LinksFunction, MetaFunction } from "@remix-run/cloudflare";
2 | import {
3 | Links,
4 | LiveReload,
5 | Meta,
6 | Outlet,
7 | Scripts,
8 | ScrollRestoration,
9 | } from "@remix-run/react";
10 | import { ServerStyleContext, ClientStyleContext } from "~/context";
11 | import { withEmotionCache } from "@emotion/react";
12 | import { useContext, useEffect } from "react";
13 | import { ChakraProvider } from "@chakra-ui/react";
14 |
15 | export const meta: MetaFunction = () => ({
16 | charset: "utf-8",
17 | title: "New Remix App",
18 | viewport: "width=device-width,initial-scale=1",
19 | });
20 |
21 | export let links: LinksFunction = () => {
22 | return [
23 | { rel: "preconnect", href: "https://fonts.googleapis.com" },
24 | { rel: "preconnect", href: "https://fonts.gstatic.com" },
25 | {
26 | rel: "stylesheet",
27 | href: "https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap",
28 | },
29 | ];
30 | };
31 |
32 | interface DocumentProps {
33 | children: React.ReactNode;
34 | }
35 |
36 | const Document = withEmotionCache(
37 | ({ children }: DocumentProps, emotionCache) => {
38 | const serverStyleData = useContext(ServerStyleContext);
39 | const clientStyleData = useContext(ClientStyleContext);
40 |
41 | // Only executed on client
42 | useEffect(() => {
43 | // re-link sheet container
44 | emotionCache.sheet.container = document.head;
45 | // re-inject tags
46 | const tags = emotionCache.sheet.tags;
47 | emotionCache.sheet.flush();
48 | tags.forEach((tag) => {
49 | (emotionCache.sheet as any)._insertTag(tag);
50 | });
51 | // reset cache to reapply global styles
52 | clientStyleData?.reset();
53 | }, []);
54 |
55 | return (
56 |
57 |
58 |
59 |
60 | {serverStyleData?.map(({ key, ids, css }) => (
61 |
66 | ))}
67 |
68 |
69 | {children}
70 |
71 |
72 |
73 |
74 |
75 | );
76 | }
77 | );
78 |
79 | export default function App() {
80 | return (
81 |
82 |
83 |
84 |
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/app/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { Box } from "@chakra-ui/react";
2 |
3 | const Index = () => {
4 | return (
5 |
6 | Hello World!
7 |
8 | );
9 | };
10 |
11 | export default Index;
12 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "sideEffects": false,
4 | "scripts": {
5 | "build": "remix build",
6 | "deploy": "wrangler publish",
7 | "dev:remix": "remix watch",
8 | "dev:miniflare": "cross-env NODE_ENV=development miniflare ./build/index.js --watch",
9 | "dev": "remix build && run-p \"dev:*\"",
10 | "start": "cross-env NODE_ENV=production miniflare ./build/index.js",
11 | "typecheck": "tsc -b",
12 | "postinstall": "remix-esbuild-override"
13 | },
14 | "dependencies": {
15 | "@chakra-ui/react": "^1.8.9",
16 | "@emotion/react": "^11.10.5",
17 | "@emotion/server": "^11.10.0",
18 | "@emotion/styled": "^11.10.5",
19 | "@remix-run/cloudflare": "^1.9.0",
20 | "@remix-run/cloudflare-workers": "^1.9.0",
21 | "@remix-run/react": "^1.9.0",
22 | "cross-env": "^7.0.3",
23 | "framer-motion": "^6.5.1",
24 | "react": "^18.2.0",
25 | "react-dom": "^18.2.0"
26 | },
27 | "devDependencies": {
28 | "@cloudflare/workers-types": "^3.18.0",
29 | "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
30 | "@remix-run/dev": "^1.9.0",
31 | "@remix-run/eslint-config": "^1.9.0",
32 | "@types/react": "^18.0.25",
33 | "@types/react-dom": "^18.0.8",
34 | "eslint": "^8.27.0",
35 | "miniflare": "^2.11.0",
36 | "npm-run-all": "^4.1.5",
37 | "remix-esbuild-override": "^3.0.4",
38 | "typescript": "^4.8.4",
39 | "wrangler": "^2.2.1"
40 | },
41 | "engines": {
42 | "node": ">=16.13"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aiji42/remix-esbuild-override/ee3863f3798f790f4a74508679b7d4737dc9c3af/examples/chakra-ui-cloudflare/public/favicon.ico
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/remix.config.js:
--------------------------------------------------------------------------------
1 | const { withEsbuildOverride } = require("remix-esbuild-override");
2 | const GlobalsPolyfills =
3 | require("@esbuild-plugins/node-globals-polyfill").default;
4 |
5 | withEsbuildOverride((option, { isServer }) => {
6 | if (isServer)
7 | option.plugins = [
8 | GlobalsPolyfills({
9 | buffer: true,
10 | }),
11 | ...option.plugins,
12 | ];
13 |
14 | return option;
15 | });
16 |
17 | /** @type {import('@remix-run/dev').AppConfig} */
18 | module.exports = {
19 | serverBuildTarget: "cloudflare-workers",
20 | server: "./server.js",
21 | devServerBroadcastDelay: 1000,
22 | ignoredRouteFiles: ["**/.*"],
23 | // appDirectory: "app",
24 | // assetsBuildDirectory: "public/build",
25 | // serverBuildPath: "build/index.js",
26 | // publicPath: "/build/",
27 | };
28 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/remix.env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/server.js:
--------------------------------------------------------------------------------
1 | import { createEventHandler } from "@remix-run/cloudflare-workers";
2 | import * as build from "@remix-run/dev/server-build";
3 |
4 | addEventListener(
5 | "fetch",
6 | createEventHandler({ build, mode: process.env.NODE_ENV })
7 | );
8 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ES2019"],
5 | "isolatedModules": true,
6 | "esModuleInterop": true,
7 | "jsx": "react-jsx",
8 | "moduleResolution": "node",
9 | "resolveJsonModule": true,
10 | "target": "ES2019",
11 | "strict": true,
12 | "allowJs": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "baseUrl": ".",
15 | "paths": {
16 | "~/*": ["./app/*"]
17 | },
18 |
19 | // Remix takes care of building everything in `remix build`.
20 | "noEmit": true
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/chakra-ui-cloudflare/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "remix-cloudflare-workers"
2 |
3 | workers_dev = true
4 | main = "./build/index.js"
5 | # https://developers.cloudflare.com/workers/platform/compatibility-dates
6 | compatibility_date = "2022-04-05"
7 |
8 | [site]
9 | bucket = "./public"
10 |
11 | [build]
12 | command = "npm run build"
13 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /** @type {import('eslint').Linter.Config} */
2 | module.exports = {
3 | extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
4 | };
5 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /build
5 | /dist
6 | /public/build
7 | /.mf
8 | .env
9 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/README.md:
--------------------------------------------------------------------------------
1 | # Remix with [emotion](https://emotion.sh/docs/introduction) on Cloudflare
2 |
3 | This example also supports css props ✌️
4 |
5 | The base template is [here](https://github.com/remix-run/examples/tree/main/emotion), and the configuration under the app directory is the same as this one.
6 | This example is for Cloudflare Workers, but it is basically the same for Cloudflare Pages.
7 |
8 | Since @emotion/server depends on Buffer, it cannot run on Cloudflare as is.
9 |
10 | ## Use remix-esbuild-override
11 |
12 | Use remix-esbuild-override and the plugin for polyfill to solve the problem.
13 |
14 | 1. Install and setup `postinstall`
15 |
16 | ```bash
17 | npm install -D remix-esbuild-override @esbuild-plugins/node-globals-polyfill
18 | # or
19 | yarn add -D remix-esbuild-override @esbuild-plugins/node-globals-polyfill
20 | ```
21 |
22 | Update `scripts > postinstall` in package.json.
23 |
24 | ```json
25 | "scripts": {
26 | "postinstall": "remix-esbuild-override"
27 | }
28 | ```
29 |
30 | **Run `npm install` or `yarn install` again to run `postinstall`.**
31 |
32 | 2. Update tsconfig.json
33 |
34 | This step is only required if `css` props is used.
35 |
36 | ```
37 | "jsx": "react-jsx",
38 | "jsxImportSource": "@emotion/react", // <= this line
39 | "moduleResolution": "node",
40 | ```
41 |
42 | 3. Update remix.config.js
43 |
44 | ```js
45 | const { withEsbuildOverride } = require("remix-esbuild-override");
46 | const GlobalsPolyfills =
47 | require("@esbuild-plugins/node-globals-polyfill").default;
48 |
49 | withEsbuildOverride((option, { isServer }) => {
50 | if (isServer)
51 | option.plugins = [
52 | GlobalsPolyfills({
53 | buffer: true,
54 | }),
55 | ...option.plugins,
56 | ];
57 |
58 | return option;
59 | });
60 |
61 | /** @type {import('@remix-run/dev').AppConfig} */
62 | module.exports = {
63 | serverBuildTarget: "cloudflare-workers",
64 | server: "./server.js",
65 | devServerBroadcastDelay: 1000,
66 | ignoredRouteFiles: ["**/.*"],
67 | // appDirectory: "app",
68 | // assetsBuildDirectory: "public/build",
69 | // serverBuildPath: "build/index.js",
70 | // publicPath: "/build/",
71 | };
72 | ```
73 |
74 | That's all.
75 |
76 | ---
77 |
78 | # Welcome to Remix!
79 |
80 | - [Remix Docs](https://remix.run/docs)
81 |
82 | ## Development
83 |
84 | You will be running two processes during development:
85 |
86 | - The Miniflare server (miniflare is a local environment for Cloudflare Workers)
87 | - The Remix development server
88 |
89 | Both are started with one command:
90 |
91 | ```sh
92 | npm run dev
93 | ```
94 |
95 | Open up [http://127.0.0.1:8787](http://127.0.0.1:8787) and you should be ready to go!
96 |
97 | If you want to check the production build, you can stop the dev server and run following commands:
98 |
99 | ```sh
100 | npm run build
101 | npm start
102 | ```
103 |
104 | Then refresh the same URL in your browser (no live reload for production builds).
105 |
106 | ## Deployment
107 |
108 | If you don't already have an account, then [create a cloudflare account here](https://dash.cloudflare.com/sign-up) and after verifying your email address with Cloudflare, go to your dashboard and set up your free custom Cloudflare Workers subdomain.
109 |
110 | Once that's done, you should be able to deploy your app:
111 |
112 | ```sh
113 | npm run deploy
114 | ```
115 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | import { RemixBrowser } from "@remix-run/react";
2 | import { startTransition, StrictMode, useCallback, useState } from "react";
3 | import { hydrateRoot } from "react-dom/client";
4 | import createEmotionCache from "~/styles/createEmotionCache";
5 | import ClientStyleContext from "~/styles/client.context";
6 | import { CacheProvider } from "@emotion/react";
7 |
8 | interface ClientCacheProviderProps {
9 | children: React.ReactNode;
10 | }
11 |
12 | function ClientCacheProvider({ children }: ClientCacheProviderProps) {
13 | const [cache, setCache] = useState(createEmotionCache());
14 |
15 | const reset = useCallback(() => {
16 | setCache(createEmotionCache());
17 | }, []);
18 |
19 | return (
20 |
21 | {children}
22 |
23 | );
24 | }
25 |
26 | function hydrate() {
27 | startTransition(() => {
28 | hydrateRoot(
29 | document,
30 |
31 |
32 |
33 |
34 |
35 | );
36 | });
37 | }
38 |
39 | if (window.requestIdleCallback) {
40 | window.requestIdleCallback(hydrate);
41 | } else {
42 | // Safari doesn't support requestIdleCallback
43 | // https://caniuse.com/requestidlecallback
44 | window.setTimeout(hydrate, 1);
45 | }
46 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/entry.server.tsx:
--------------------------------------------------------------------------------
1 | import type { EntryContext } from "@remix-run/cloudflare";
2 | import { RemixServer } from "@remix-run/react";
3 | import { renderToString } from "react-dom/server";
4 | import createEmotionCache from "~/styles/createEmotionCache";
5 | import createEmotionServer from "@emotion/server/create-instance";
6 | import ServerStyleContext from "~/styles/server.context";
7 | import { CacheProvider } from "@emotion/react";
8 |
9 | export default function handleRequest(
10 | request: Request,
11 | responseStatusCode: number,
12 | responseHeaders: Headers,
13 | remixContext: EntryContext
14 | ) {
15 | const cache = createEmotionCache();
16 | const { extractCriticalToChunks } = createEmotionServer(cache);
17 |
18 | const html = renderToString(
19 |
20 |
21 |
22 |
23 |
24 | );
25 |
26 | const chunks = extractCriticalToChunks(html);
27 |
28 | const markup = renderToString(
29 |
30 |
31 |
32 |
33 |
34 | );
35 |
36 | responseHeaders.set("Content-Type", "text/html");
37 |
38 | return new Response(`${markup}`, {
39 | status: responseStatusCode,
40 | headers: responseHeaders,
41 | });
42 | }
43 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/root.tsx:
--------------------------------------------------------------------------------
1 | import type { MetaFunction } from "@remix-run/cloudflare";
2 | import {
3 | Links,
4 | LiveReload,
5 | Meta,
6 | Outlet,
7 | Scripts,
8 | ScrollRestoration,
9 | useCatch,
10 | } from "@remix-run/react";
11 | import { withEmotionCache } from "@emotion/react";
12 | import { useContext, useEffect } from "react";
13 | import ServerStyleContext from "~/styles/server.context";
14 | import ClientStyleContext from "~/styles/client.context";
15 | import styled from "@emotion/styled";
16 |
17 | const Container = styled("div")`
18 | background-color: #ff0000;
19 | padding: 1em;
20 | `;
21 |
22 | export const meta: MetaFunction = () => ({
23 | charset: "utf-8",
24 | title: "Remix with Emotion",
25 | viewport: "width=device-width,initial-scale=1",
26 | });
27 |
28 | interface DocumentProps {
29 | children: React.ReactNode;
30 | title?: string;
31 | }
32 |
33 | const Document = withEmotionCache(
34 | ({ children, title }: DocumentProps, emotionCache) => {
35 | const serverStyleData = useContext(ServerStyleContext);
36 | const clientStyleData = useContext(ClientStyleContext);
37 |
38 | // Only executed on client
39 | useEffect(() => {
40 | // re-link sheet container
41 | emotionCache.sheet.container = document.head;
42 |
43 | // re-inject tags
44 | const tags = emotionCache.sheet.tags;
45 | emotionCache.sheet.flush();
46 | tags.forEach((tag) => {
47 | (emotionCache.sheet as any)._insertTag(tag);
48 | });
49 |
50 | // reset cache to re-apply global styles
51 | clientStyleData.reset();
52 | }, []);
53 |
54 | return (
55 |
56 |
57 | {title ? {title} : null}
58 |
59 |
60 | {serverStyleData?.map(({ key, ids, css }) => (
61 |
67 | ))}
68 |
69 |
70 | {children}
71 |
72 |
73 |
74 |
75 |
76 | );
77 | }
78 | );
79 |
80 | export default function App() {
81 | return (
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | export function CatchBoundary() {
89 | const caught = useCatch();
90 |
91 | return (
92 |
93 |
94 |
95 | [CatchBoundary]: {caught.status} {caught.statusText}
96 |
97 |
98 |
99 | );
100 | }
101 |
102 | export function ErrorBoundary({ error }: { error: Error }) {
103 | return (
104 |
105 |
106 | [ErrorBoundary]: There was an error: {error.message}
107 |
108 |
109 | );
110 | }
111 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 | import { Link } from "@remix-run/react";
3 |
4 | const Container = styled("div")`
5 | font-family: "system-ui, sans-serif";
6 | line-height: 1.4;
7 | background-color: #ddd;
8 | `;
9 |
10 | export default function Index() {
11 | return (
12 |
13 | Welcome to Remix with Emotion Example
14 |
15 | -
16 | Using css props
17 |
18 | -
19 | Jokes
20 |
21 | -
22 | Jokes: Error
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/routes/jokes-error.tsx:
--------------------------------------------------------------------------------
1 | export default function JokesError() {
2 | throw new Error("This route is no joking with us.");
3 | }
4 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/routes/jokes.tsx:
--------------------------------------------------------------------------------
1 | import styled from "@emotion/styled";
2 | import { Link } from "@remix-run/react";
3 |
4 | const Container = styled("div")`
5 | background-color: #d6d6d6;
6 | `;
7 |
8 | export default function Jokes() {
9 | return (
10 |
11 | Jokes
12 | This route works fine.
13 | Back to home
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/routes/use-css-props.tsx:
--------------------------------------------------------------------------------
1 | import { Link } from "@remix-run/react";
2 | import { css } from "@emotion/react";
3 |
4 | const style = css`
5 | background-color: #d6d6d6;
6 | `;
7 |
8 | const style2 = css`
9 | background-color: #d8eaac;
10 | `;
11 |
12 | export default function Jokes() {
13 | return (
14 | <>
15 |
16 |
Using css props
17 |
This route works fine.
18 |
Back to home
19 |
20 | you can use shorthand Fragment ({"<>>"}) without pragma.
21 | >
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/styles/client.context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | export interface ClientStyleContextData {
4 | reset: () => void;
5 | }
6 |
7 | const ClientStyleContext = createContext({
8 | reset: () => {},
9 | });
10 |
11 | export default ClientStyleContext;
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/styles/createEmotionCache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | export default function createEmotionCache() {
4 | return createCache({ key: "remix-css" });
5 | }
6 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/app/styles/server.context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | export interface ServerStyleContextData {
4 | key: string;
5 | ids: Array;
6 | css: string;
7 | }
8 |
9 | const ServerStyleContext = createContext(null);
10 |
11 | export default ServerStyleContext;
12 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "sideEffects": false,
4 | "scripts": {
5 | "build": "remix build",
6 | "deploy": "wrangler publish",
7 | "dev:remix": "remix watch",
8 | "dev:miniflare": "cross-env NODE_ENV=development miniflare ./build/index.js --watch",
9 | "dev": "remix build && run-p \"dev:*\"",
10 | "start": "cross-env NODE_ENV=production miniflare ./build/index.js",
11 | "typecheck": "tsc -b",
12 | "postinstall": "remix-esbuild-override"
13 | },
14 | "dependencies": {
15 | "@emotion/cache": "^11.10.5",
16 | "@emotion/react": "^11.10.5",
17 | "@emotion/server": "^11.10.0",
18 | "@emotion/styled": "^11.10.5",
19 | "@remix-run/cloudflare": "^1.9.0",
20 | "@remix-run/cloudflare-workers": "^1.9.0",
21 | "@remix-run/react": "^1.9.0",
22 | "cross-env": "^7.0.3",
23 | "react": "^18.2.0",
24 | "react-dom": "^18.2.0"
25 | },
26 | "devDependencies": {
27 | "@cloudflare/workers-types": "^3.18.0",
28 | "@esbuild-plugins/node-globals-polyfill": "^0.1.1",
29 | "@remix-run/dev": "^1.9.0",
30 | "@remix-run/eslint-config": "^1.9.0",
31 | "@types/react": "^18.0.25",
32 | "@types/react-dom": "^18.0.8",
33 | "eslint": "^8.27.0",
34 | "miniflare": "^2.11.0",
35 | "npm-run-all": "^4.1.5",
36 | "remix-esbuild-override": "^3.0.4",
37 | "typescript": "^4.8.4",
38 | "wrangler": "^2.2.1"
39 | },
40 | "engines": {
41 | "node": ">=16.13"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aiji42/remix-esbuild-override/ee3863f3798f790f4a74508679b7d4737dc9c3af/examples/emotion-cloudflare/public/favicon.ico
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/remix.config.js:
--------------------------------------------------------------------------------
1 | const { withEsbuildOverride } = require("remix-esbuild-override");
2 | const GlobalsPolyfills =
3 | require("@esbuild-plugins/node-globals-polyfill").default;
4 |
5 | withEsbuildOverride((option, { isServer }) => {
6 | if (isServer)
7 | option.plugins = [
8 | GlobalsPolyfills({
9 | buffer: true,
10 | }),
11 | ...option.plugins,
12 | ];
13 |
14 | return option;
15 | });
16 |
17 | /** @type {import('@remix-run/dev').AppConfig} */
18 | module.exports = {
19 | serverBuildTarget: "cloudflare-workers",
20 | server: "./server.js",
21 | devServerBroadcastDelay: 1000,
22 | ignoredRouteFiles: ["**/.*"],
23 | // appDirectory: "app",
24 | // assetsBuildDirectory: "public/build",
25 | // serverBuildPath: "build/index.js",
26 | // publicPath: "/build/",
27 | };
28 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/remix.env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/server.js:
--------------------------------------------------------------------------------
1 | import { createEventHandler } from "@remix-run/cloudflare-workers";
2 | import * as build from "@remix-run/dev/server-build";
3 |
4 | addEventListener(
5 | "fetch",
6 | createEventHandler({ build, mode: process.env.NODE_ENV })
7 | );
8 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ES2019"],
5 | "isolatedModules": true,
6 | "esModuleInterop": true,
7 | "jsx": "react-jsx",
8 | "jsxImportSource": "@emotion/react",
9 | "moduleResolution": "node",
10 | "resolveJsonModule": true,
11 | "target": "ES2019",
12 | "strict": true,
13 | "allowJs": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "baseUrl": ".",
16 | "paths": {
17 | "~/*": ["./app/*"]
18 | },
19 |
20 | // Remix takes care of building everything in `remix build`.
21 | "noEmit": true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/emotion-cloudflare/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "remix-cloudflare-workers"
2 |
3 | workers_dev = true
4 | main = "./build/index.js"
5 | # https://developers.cloudflare.com/workers/platform/compatibility-dates
6 | compatibility_date = "2022-04-05"
7 |
8 | [site]
9 | bucket = "./public"
10 |
11 | [build]
12 | command = "npm run build"
13 |
--------------------------------------------------------------------------------
/examples/styled-components/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@remix-run/eslint-config"]
3 | }
4 |
--------------------------------------------------------------------------------
/examples/styled-components/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | /.cache
4 | /build
5 | /dist
6 | /public/build
7 | /.mf
8 | .env
9 |
--------------------------------------------------------------------------------
/examples/styled-components/README.md:
--------------------------------------------------------------------------------
1 | # Remix with [Styled-Components](https://styled-components.com/)
2 |
3 | ## Use remix-esbuild-override
4 |
5 | To use Styled-Components, the esbuild plugin must be adapted, so install `remix-esbuild-override` to make esbuild extensible.
6 |
7 | #### 1. Install and setup `postinstall`
8 |
9 | ```bash
10 | npm install -D remix-esbuild-override
11 | # or
12 | yarn add -D remix-esbuild-override
13 | ```
14 |
15 | Update `scripts > postinstall` in package.json.
16 |
17 | ```json
18 | "scripts": {
19 | "postinstall": "remix-esbuild-override"
20 | }
21 | ```
22 |
23 | **Run `npm install` or `yarn install` again to run `postinstall`.**
24 |
25 | #### 2. Install Styled-Components
26 |
27 | ```bash
28 | npm install -S styled-components
29 | # or
30 | yarn add styled-components
31 | ```
32 |
33 | And if you want types:
34 |
35 | ```bash
36 | npm install -D @types/styled-components
37 | # or
38 | yarn add -D @types/styled-components
39 | ```
40 |
41 | #### 3. Create a plugin for esbuild
42 |
43 | There is [an unofficial esbuild plugin for Styled-Components](https://gist.github.com/hyrious/4fcc3680e7f9998377c7eab42487791a) reusing the official Babel transformer plugin, [`babel-plugin-styled-components`](https://github.com/styled-components/babel-plugin-styled-components).
44 |
45 | Copy [this file](https://github.com/aiji42/remix-esbuild-override/tree/main/examples/styled-components/styled-components.js) and place it in the root of the project (same directory as remix.config.js).
46 |
47 | Install dependencies for the plugin above:
48 |
49 | ```bash
50 | npm install -D @babel/core babel-plugin-styled-components
51 | # or
52 | yarn add -D @babel/core babel-plugin-styled-components
53 | ```
54 |
55 |
56 | #### 4. Update remix.config.js
57 |
58 | ```js
59 | const { withEsbuildOverride } = require("remix-esbuild-override");
60 | const styledComponentsPlugin = require("./styled-components-esbuild-plugin");
61 |
62 | withEsbuildOverride((option) => {
63 | option.plugins.unshift(styledComponentsPlugin());
64 |
65 | return option;
66 | });
67 |
68 | /**
69 | * @type {import('@remix-run/dev').AppConfig}
70 | */
71 | module.exports = {
72 | ignoredRouteFiles: [".*"],
73 | appDirectory: "app",
74 | assetsBuildDirectory: "public/build",
75 | serverBuildPath: "build/index.js",
76 | publicPath: "/build/",
77 | };
78 | ```
79 |
80 | #### 5. Use Styled as normal
81 |
82 | You can now create styles with Styled-Components as normal as you get all the benefits of server-side rendered stylesheets including perfect hydration and named styles while in debug-mode:
83 |
84 | ```ts
85 | import styled from 'styled-components';
86 |
87 | const Heading = styled.h1`
88 | color: hotpink;
89 | `;
90 |
91 | export default function Index() {
92 | return Welcome to Remix using Styled-Components;
93 | }
94 | ```
95 |
96 | That's all.
97 |
98 | ---
99 |
100 | # Welcome to Remix!
101 |
102 | - [Remix Docs](https://remix.run/docs)
103 |
104 | ## Development
105 |
106 | You will be running two processes during development:
107 |
108 | - The Miniflare server (miniflare is a local environment for Cloudflare Workers)
109 | - The Remix development server
110 |
111 | Both are started with one command:
112 |
113 | ```sh
114 | npm run dev
115 | ```
116 |
117 | Open up [http://127.0.0.1:8787](http://127.0.0.1:8787) and you should be ready to go!
118 |
119 | If you want to check the production build, you can stop the dev server and run following commands:
120 |
121 | ```sh
122 | npm run build
123 | npm start
124 | ```
125 |
126 | Then refresh the same URL in your browser (no live reload for production builds).
127 |
128 | ## Deployment
129 |
130 | Use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) to build and deploy your application to Cloudflare Workers. If you don't have it yet, follow [the installation guide](https://developers.cloudflare.com/workers/cli-wrangler/install-update) to get it setup. Be sure to [authenticate the CLI](https://developers.cloudflare.com/workers/cli-wrangler/authentication) as well.
131 |
132 | If you don't already have an account, then [create a cloudflare account here](https://dash.cloudflare.com/sign-up) and after verifying your email address with Cloudflare, go to your dashboard and set up your free custom Cloudflare Workers subdomain.
133 |
134 | Once that's done, you should be able to deploy your app:
135 |
136 | ```sh
137 | npm run deploy
138 | ```
139 |
--------------------------------------------------------------------------------
/examples/styled-components/app/entry.client.tsx:
--------------------------------------------------------------------------------
1 | import { RemixBrowser } from "@remix-run/react";
2 | import { hydrate } from "react-dom";
3 |
4 | hydrate(, document);
5 |
--------------------------------------------------------------------------------
/examples/styled-components/app/entry.server.tsx:
--------------------------------------------------------------------------------
1 | import { RemixServer } from "@remix-run/react";
2 | import { renderToString } from "react-dom/server";
3 | import type { EntryContext } from "@remix-run/react/entry";
4 |
5 | export default function handleRequest(
6 | request: Request,
7 | responseStatusCode: number,
8 | responseHeaders: Headers,
9 | remixContext: EntryContext
10 | ) {
11 | let markup = renderToString(
12 |
13 | );
14 |
15 | responseHeaders.set("Content-Type", "text/html");
16 |
17 | return new Response("" + markup, {
18 | status: responseStatusCode,
19 | headers: responseHeaders,
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/examples/styled-components/app/root.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Links,
3 | LiveReload,
4 | Meta,
5 | Outlet,
6 | Scripts,
7 | ScrollRestoration,
8 | } from "@remix-run/react";
9 |
10 | export const meta = () => ({
11 | charset: "utf-8",
12 | title: "New Remix App",
13 | viewport: "width=device-width,initial-scale=1",
14 | });
15 |
16 | export default function App() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/examples/styled-components/app/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Heading = styled.h1`
4 | color: hotpink;
5 | `;
6 |
7 | export default function Index() {
8 | return (
9 |
10 |
Welcome to Remix using Styled-Components
11 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/examples/styled-components/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remix-template-cloudflare-workers",
3 | "private": true,
4 | "description": "",
5 | "license": "",
6 | "sideEffects": false,
7 | "main": "build/index.js",
8 | "scripts": {
9 | "build": "remix build",
10 | "dev": "remix dev",
11 | "start": "remix-serve build",
12 | "typecheck": "tsc -b",
13 | "postinstall": "remix-esbuild-override"
14 | },
15 | "dependencies": {
16 | "@babel/core": "^7.19.3",
17 | "@parcel/css": "^1.10.1",
18 | "@remix-run/node": "^1.6.0",
19 | "@remix-run/react": "^1.6.0",
20 | "@remix-run/serve": "^1.6.0",
21 | "@storyblok/react": "^1.0.4",
22 | "babel-plugin-styled-components": "^2.0.7",
23 | "browserslist": "^4.20.4",
24 | "react": "^17.0.2",
25 | "react-dom": "^17.0.2",
26 | "styled-components": "^5.3.6"
27 | },
28 | "devDependencies": {
29 | "@remix-run/dev": "^1.6.0",
30 | "@remix-run/eslint-config": "^1.6.0",
31 | "@types/react": "^18.0.12",
32 | "@types/react-dom": "^18.0.5",
33 | "@types/styled-components": "^5.1.26",
34 | "eslint": "^8.11.0",
35 | "remix-esbuild-override": "^3.0.4",
36 | "typescript": "^4.7.3"
37 | },
38 | "engines": {
39 | "node": ">=14"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/examples/styled-components/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aiji42/remix-esbuild-override/ee3863f3798f790f4a74508679b7d4737dc9c3af/examples/styled-components/public/favicon.ico
--------------------------------------------------------------------------------
/examples/styled-components/remix.config.js:
--------------------------------------------------------------------------------
1 | const { withEsbuildOverride } = require("remix-esbuild-override");
2 | const styledComponentsPlugin = require("./styled-components-esbuild-plugin");
3 |
4 | withEsbuildOverride((option) => {
5 | option.plugins.unshift(styledComponentsPlugin());
6 |
7 | return option;
8 | });
9 |
10 | /**
11 | * @type {import('@remix-run/dev').AppConfig}
12 | */
13 | module.exports = {
14 | ignoredRouteFiles: [".*"],
15 | // appDirectory: "app",
16 | // assetsBuildDirectory: "public/build",
17 | // serverBuildPath: "build/index.js",
18 | // publicPath: "/build/",
19 | };
20 |
--------------------------------------------------------------------------------
/examples/styled-components/remix.env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/examples/styled-components/styled-components-esbuild-plugin.js:
--------------------------------------------------------------------------------
1 | const babel = require("@babel/core");
2 | const styled = require("babel-plugin-styled-components");
3 | const fs = require("node:fs");
4 | const path = require("path");
5 |
6 | function styledComponentsPlugin() {
7 | return {
8 | name: "styled-components",
9 | setup({ onLoad }) {
10 | const root = process.cwd();
11 | onLoad({ filter: /\.[tj]sx$/ }, async (args) => {
12 | let code = await fs.promises.readFile(args.path, "utf8");
13 | let plugins = [
14 | "importMeta",
15 | "topLevelAwait",
16 | "classProperties",
17 | "classPrivateProperties",
18 | "classPrivateMethods",
19 | "jsx",
20 | ];
21 | let loader = "jsx";
22 | if (args.path.endsWith(".tsx")) {
23 | plugins.push("typescript");
24 | loader = "tsx";
25 | }
26 | const result = await babel.transformAsync(code, {
27 | babelrc: false,
28 | configFile: false,
29 | ast: false,
30 | root,
31 | filename: args.path,
32 | parserOpts: {
33 | sourceType: "module",
34 | allowAwaitOutsideFunction: true,
35 | plugins,
36 | },
37 | generatorOpts: {
38 | decoratorsBeforeExport: true,
39 | },
40 | plugins: [styled],
41 | sourceMaps: true,
42 | inputSourceMap: false,
43 | });
44 | return {
45 | contents:
46 | result.code +
47 | `//# sourceMappingURL=data:application/json;base64,` +
48 | Buffer.from(JSON.stringify(result.map)).toString("base64"),
49 | loader,
50 | resolveDir: path.dirname(args.path),
51 | };
52 | });
53 | },
54 | };
55 | }
56 |
57 | module.exports = styledComponentsPlugin;
58 |
--------------------------------------------------------------------------------
/examples/styled-components/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
3 | "compilerOptions": {
4 | "lib": ["DOM", "DOM.Iterable", "ES2019"],
5 | "isolatedModules": true,
6 | "esModuleInterop": true,
7 | "jsx": "react-jsx",
8 | "moduleResolution": "node",
9 | "resolveJsonModule": true,
10 | "target": "ES2019",
11 | "strict": true,
12 | "baseUrl": ".",
13 | "paths": {
14 | "~/*": ["./app/*"]
15 | },
16 | "noEmit": true,
17 | "allowJs": true,
18 | "forceConsistentCasingInFileNames": true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "remix-esbuild-override",
3 | "version": "1.0.0",
4 | "description": "This library allows Remix compiler (esbuild) settings to be overridden.",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "bin": {
8 | "remix-esbuild-override": "dist/bin/esbuild-override.js"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "repository": "https://github.com/aiji42/remix-esbuild-override.git",
14 | "author": "aiji42 (https://twitter.com/aiji42_dev)",
15 | "license": "MIT",
16 | "scripts": {
17 | "build": "tsc",
18 | "format": "prettier -w src",
19 | "semantic-release": "semantic-release",
20 | "prepare": "husky install",
21 | "test": "vitest",
22 | "test:run": "vitest run",
23 | "test:coverage": "vitest run --coverage"
24 | },
25 | "peerDependencies": {
26 | "@remix-run/dev": ">=1.2.0"
27 | },
28 | "devDependencies": {
29 | "@commitlint/cli": "^16.2.3",
30 | "@commitlint/config-conventional": "^16.2.1",
31 | "@types/node": "^17.0.25",
32 | "c8": "^7.11.2",
33 | "esbuild": "0.17.18",
34 | "husky": "^7.0.4",
35 | "lint-staged": "^12.4.0",
36 | "memfs": "^3.4.1",
37 | "prettier": "^2.6.2",
38 | "semantic-release": "^19.0.5",
39 | "semantic-release-cli": "^5.4.4",
40 | "typescript": "^4.6.3",
41 | "vitest": "^0.9.3"
42 | },
43 | "lint-staged": {
44 | "*.{js,ts,md,json}": "prettier --write"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/__tests__/__snapshots__/patching.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1
2 |
3 | exports[`patching > Only esbuild directly under node_modules 1`] = `
4 | {
5 | "/app/node_modules/esbuild/lib/main.js": "
6 | var __create = Object.create;
7 | var __defProp = Object.defineProperty;
8 | __defProp = (a, b, c) => Object.defineProperty(a, b, { ...c, configurable: true });
9 | var __defProps = Object.defineProperties;
10 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
12 | var __getOwnPropNames = Object.getOwnPropertyNames;
13 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
14 | var __getProtoOf = Object.getPrototypeOf;
15 | var __hasOwnProp = Object.prototype.hasOwnProperty;
16 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
17 | ",
18 | "/app/node_modules/esbuild/package.json": "{ \\"name\\": \\"esbuild\\", \\"main\\": \\"lib/main.js\\" }",
19 | }
20 | `;
21 |
22 | exports[`patching > Only esbuild under @remix-run/dev 1`] = `
23 | {
24 | "/app/node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js": "
25 | var __create = Object.create;
26 | var __defProp = Object.defineProperty;
27 | __defProp = (a, b, c) => Object.defineProperty(a, b, { ...c, configurable: true });
28 | var __defProps = Object.defineProperties;
29 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
30 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
31 | var __getOwnPropNames = Object.getOwnPropertyNames;
32 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
33 | var __getProtoOf = Object.getPrototypeOf;
34 | var __hasOwnProp = Object.prototype.hasOwnProperty;
35 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
36 | ",
37 | "/app/node_modules/@remix-run/dev/node_modules/esbuild/package.json": "{ \\"name\\": \\"esbuild\\", \\"main\\": \\"lib/main.js\\" }",
38 | }
39 | `;
40 |
41 | exports[`patching > esbuild exists in both 1`] = `
42 | {
43 | "/app/node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js": "
44 | var __create = Object.create;
45 | var __defProp = Object.defineProperty;
46 | __defProp = (a, b, c) => Object.defineProperty(a, b, { ...c, configurable: true });
47 | var __defProps = Object.defineProperties;
48 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
49 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
50 | var __getOwnPropNames = Object.getOwnPropertyNames;
51 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
52 | var __getProtoOf = Object.getPrototypeOf;
53 | var __hasOwnProp = Object.prototype.hasOwnProperty;
54 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
55 | ",
56 | "/app/node_modules/@remix-run/dev/node_modules/esbuild/package.json": "{ \\"name\\": \\"esbuild\\", \\"main\\": \\"lib/main.js\\" }",
57 | "/app/node_modules/esbuild/lib/main.js": "
58 | var __create = Object.create;
59 | var __defProp = Object.defineProperty;
60 | var __defProps = Object.defineProperties;
61 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
62 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
63 | var __getOwnPropNames = Object.getOwnPropertyNames;
64 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
65 | var __getProtoOf = Object.getPrototypeOf;
66 | var __hasOwnProp = Object.prototype.hasOwnProperty;
67 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
68 | ",
69 | "/app/node_modules/esbuild/package.json": "{ \\"name\\": \\"esbuild\\", \\"main\\": \\"lib/main.js\\" }",
70 | }
71 | `;
72 |
73 | exports[`patching > patched already 1`] = `
74 | {
75 | "/app/node_modules/esbuild/lib/main.js": "
76 | var __create = Object.create;
77 | var __defProp = Object.defineProperty;
78 | var __defProps = Object.defineProperties;
79 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
80 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
81 | var __getOwnPropNames = Object.getOwnPropertyNames;
82 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
83 | var __getProtoOf = Object.getPrototypeOf;
84 | var __hasOwnProp = Object.prototype.hasOwnProperty;
85 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
86 | __defProp = (a, b, c) => Object.defineProperty(a, b, { ...c, configurable: true });",
87 | "/app/node_modules/esbuild/package.json": "{ \\"name\\": \\"esbuild\\", \\"main\\": \\"lib/main.js\\" }",
88 | }
89 | `;
90 |
--------------------------------------------------------------------------------
/src/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { withEsbuildOverride, esbuildOverrideOption } from "../index";
2 | import * as utils from "../utils";
3 | import { beforeEach, describe, test } from "vitest";
4 |
5 | vi.mock("../utils");
6 |
7 | describe("index", () => {
8 | afterEach(() => {
9 | vi.resetAllMocks();
10 | });
11 |
12 | describe("esbuildOverrideOption", () => {
13 | beforeAll(() => {
14 | vi.spyOn(utils, "load").mockReturnValue(null);
15 | withEsbuildOverride((option, { isServer, isDev }) => {
16 | // @ts-ignore
17 | option.runtime = isServer ? "server" : "browser";
18 | // @ts-ignore
19 | option.mode = isDev ? "development" : "production";
20 | return option;
21 | });
22 | });
23 |
24 | test("production/browser", () => {
25 | expect(esbuildOverrideOption({})).toEqual({
26 | mode: "production",
27 | runtime: "browser",
28 | });
29 | });
30 |
31 | test("production/server", () => {
32 | expect(esbuildOverrideOption({ write: false })).toEqual({
33 | mode: "production",
34 | runtime: "server",
35 | write: false,
36 | });
37 | });
38 |
39 | test("development/browser", () => {
40 | expect(
41 | esbuildOverrideOption({
42 | define: { "process.env.NODE_ENV": "development" },
43 | })
44 | ).toEqual({
45 | mode: "development",
46 | runtime: "browser",
47 | define: { "process.env.NODE_ENV": "development" },
48 | });
49 | });
50 |
51 | test("development/server", () => {
52 | expect(
53 | esbuildOverrideOption({
54 | write: false,
55 | define: { "process.env.NODE_ENV": "development" },
56 | })
57 | ).toEqual({
58 | mode: "development",
59 | runtime: "server",
60 | write: false,
61 | define: { "process.env.NODE_ENV": "development" },
62 | });
63 | });
64 |
65 | test("defined invalid callback", () => {
66 | // @ts-ignore
67 | withEsbuildOverride(() => {});
68 |
69 | expect(() => esbuildOverrideOption({})).toThrowError(
70 | /The callback function withEsbuildOverride must return the esbuild option value/
71 | );
72 | });
73 | });
74 |
75 | describe("withEsbuildOverride", () => {
76 | let esbuild: any;
77 | let mockedBuildFunction = vi.fn();
78 | let mockedContextFunction = vi.fn();
79 | beforeEach(() => {
80 | esbuild = undefined;
81 | vi.spyOn(utils, "load").mockImplementation((mod) => {
82 | if (mod === "@remix-run/dev/node_modules/esbuild") return esbuild;
83 | return null;
84 | });
85 | });
86 |
87 | test("override esbuild.build", () => {
88 | esbuild = { build: mockedBuildFunction };
89 | withEsbuildOverride((option) => {
90 | option.jsxFactory = "jsx";
91 | return option;
92 | });
93 | esbuild.build({ foo: "bar" });
94 |
95 | expect(mockedBuildFunction).toBeCalledWith({
96 | foo: "bar",
97 | jsxFactory: "jsx",
98 | });
99 | expect(esbuild.overridden).toEqual(true);
100 | });
101 |
102 | test("override esbuild.context", () => {
103 | esbuild = { context: mockedContextFunction };
104 | withEsbuildOverride((option) => {
105 | option.jsxFactory = "jsx";
106 | return option;
107 | });
108 | esbuild.context({ foo: "bar" });
109 |
110 | expect(mockedContextFunction).toBeCalledWith({
111 | foo: "bar",
112 | jsxFactory: "jsx",
113 | });
114 | expect(esbuild.overridden).toEqual(true);
115 | });
116 |
117 | test("esbuild has already been overwritten", () => {
118 | esbuild = {
119 | build: mockedBuildFunction,
120 | context: mockedContextFunction,
121 | overridden: true,
122 | };
123 | withEsbuildOverride((option) => {
124 | option.jsxFactory = "jsx";
125 | return option;
126 | });
127 | esbuild.build({ foo: "bar" });
128 | esbuild.context({ foo: "bar" });
129 |
130 | expect(mockedBuildFunction).toBeCalledWith({
131 | foo: "bar",
132 | });
133 | expect(mockedContextFunction).toBeCalledWith({
134 | foo: "bar",
135 | });
136 | expect(esbuild.overridden).toEqual(true);
137 | });
138 |
139 | test("Unable to override esbuild", () => {
140 | const obj = {};
141 | Object.defineProperty(obj, "build", { get: () => mockedBuildFunction });
142 | vi.spyOn(utils, "load").mockReturnValue(obj);
143 | const mockedConsole = vi.spyOn(console, "error");
144 | expect(() => withEsbuildOverride((option) => option)).toThrowError(
145 | /Override of esbuild failed/
146 | );
147 | expect(mockedConsole).toBeCalledWith(
148 | expect.stringMatching(/Override of esbuild failed/)
149 | );
150 | });
151 |
152 | test("not defined callback", () => {
153 | const mockedConsole = vi.spyOn(console, "warn");
154 | withEsbuildOverride();
155 | expect(mockedConsole).toBeCalledWith(
156 | expect.stringMatching(/esbuild is not overridden/)
157 | );
158 | });
159 | });
160 | });
161 |
--------------------------------------------------------------------------------
/src/__tests__/patching.test.ts:
--------------------------------------------------------------------------------
1 | import { fs, vol } from "memfs";
2 | import { patching } from "../patching";
3 | import * as utils from "../utils";
4 | import { defPropRedefine } from "../constants";
5 | import { describe, test } from "vitest";
6 |
7 | const esbuildMainJs = `
8 | var __create = Object.create;
9 | var __defProp = Object.defineProperty;
10 | var __defProps = Object.defineProperties;
11 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
13 | var __getOwnPropNames = Object.getOwnPropertyNames;
14 | var __getOwnPropSymbols = Object.getOwnPropertySymbols;
15 | var __getProtoOf = Object.getPrototypeOf;
16 | var __hasOwnProp = Object.prototype.hasOwnProperty;
17 | var __propIsEnum = Object.prototype.propertyIsEnumerable;
18 | `;
19 |
20 | vi.mock("../utils");
21 | vi.mock("fs", () => fs);
22 | // const mockedConsoleLog = vi.spyOn(console, "log");
23 |
24 | describe("patching", () => {
25 | beforeEach(() => {
26 | vol.reset();
27 | });
28 | afterEach(() => {
29 | vi.resetAllMocks();
30 | });
31 |
32 | test("Only esbuild directly under node_modules", () => {
33 | vol.fromJSON(
34 | {
35 | "./node_modules/esbuild/package.json":
36 | '{ "name": "esbuild", "main": "lib/main.js" }',
37 | "./node_modules/esbuild/lib/main.js": esbuildMainJs,
38 | },
39 | "/app"
40 | );
41 | vi.spyOn(utils, "resolve").mockImplementation((mod) => {
42 | if (mod === "esbuild") return "/app/node_modules/esbuild/lib/main.js";
43 | return null;
44 | });
45 |
46 | patching();
47 |
48 | expect(vol.toJSON()).toMatchSnapshot();
49 | });
50 |
51 | test("Only esbuild under @remix-run/dev", () => {
52 | vol.fromJSON(
53 | {
54 | "./node_modules/@remix-run/dev/node_modules/esbuild/package.json":
55 | '{ "name": "esbuild", "main": "lib/main.js" }',
56 | "./node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js":
57 | esbuildMainJs,
58 | },
59 | "/app"
60 | );
61 | vi.spyOn(utils, "resolve").mockImplementation((mod) => {
62 | if (mod === "@remix-run/dev/node_modules/esbuild")
63 | return "/app/node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js";
64 | return null;
65 | });
66 |
67 | patching();
68 |
69 | expect(vol.toJSON()).toMatchSnapshot();
70 | });
71 |
72 | test("esbuild exists in both", () => {
73 | vol.fromJSON(
74 | {
75 | "./node_modules/esbuild/package.json":
76 | '{ "name": "esbuild", "main": "lib/main.js" }',
77 | "./node_modules/esbuild/lib/main.js": esbuildMainJs,
78 | "./node_modules/@remix-run/dev/node_modules/esbuild/package.json":
79 | '{ "name": "esbuild", "main": "lib/main.js" }',
80 | "./node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js":
81 | esbuildMainJs,
82 | },
83 | "/app"
84 | );
85 | vi.spyOn(utils, "resolve").mockImplementation((mod) => {
86 | if (mod === "@remix-run/dev/node_modules/esbuild")
87 | return "/app/node_modules/@remix-run/dev/node_modules/esbuild/lib/main.js";
88 | return null;
89 | });
90 |
91 | patching();
92 |
93 | expect(vol.toJSON()).toMatchSnapshot();
94 | });
95 |
96 | test("defProPattern missing from script", () => {
97 | vol.fromJSON(
98 | {
99 | "./node_modules/esbuild/package.json":
100 | '{ "name": "esbuild", "main": "lib/main.js" }',
101 | "./node_modules/esbuild/lib/main.js": "",
102 | },
103 | "/app"
104 | );
105 | vi.spyOn(utils, "resolve").mockImplementation((mod) => {
106 | if (mod === "esbuild") return "/app/node_modules/esbuild/lib/main.js";
107 | return null;
108 | });
109 |
110 | expect(() => patching()).toThrowError(
111 | /esbuild patch by remix-esbuild-override failed/
112 | );
113 | });
114 |
115 | test("patched already", () => {
116 | const mockedConsole = vi.spyOn(console, "log");
117 | vol.fromJSON(
118 | {
119 | "./node_modules/esbuild/package.json":
120 | '{ "name": "esbuild", "main": "lib/main.js" }',
121 | "./node_modules/esbuild/lib/main.js": esbuildMainJs + defPropRedefine,
122 | },
123 | "/app"
124 | );
125 | vi.spyOn(utils, "resolve").mockImplementation((mod) => {
126 | if (mod === "esbuild") return "/app/node_modules/esbuild/lib/main.js";
127 | return null;
128 | });
129 |
130 | patching();
131 |
132 | expect(vol.toJSON()).toMatchSnapshot();
133 | expect(mockedConsole).toBeCalledWith(
134 | expect.stringMatching(
135 | /esbuild patch by remix-esbuild-override is complete/
136 | )
137 | );
138 | });
139 | });
140 |
--------------------------------------------------------------------------------
/src/bin/esbuild-override.ts:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | import { patching } from "../patching";
3 |
4 | patching();
5 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | export const mods = ["@remix-run/dev/node_modules/esbuild", "esbuild"];
2 | export const defProPattern = /__defProp = Object\.defineProperty;/;
3 | export const defPropRedefine = `__defProp = (a, b, c) => Object.defineProperty(a, b, { ...c, configurable: true });`;
4 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import type { BuildOptions, build, context } from "esbuild";
2 | import { load } from "./utils";
3 | import { mods } from "./constants";
4 |
5 | type BrowserBuildOption = BuildOptions;
6 | type ServerBuildOption = BuildOptions & { write: false };
7 | type EsbuildOption = BrowserBuildOption | ServerBuildOption;
8 | /**
9 | * @property {boolean} isServer - Whether the esbuild call is for server-side
10 | * @property {boolean} isDev - Whether the esbuild call is for a development
11 | */
12 | type EsbuildContext = {
13 | isServer: boolean;
14 | isDev: boolean;
15 | };
16 |
17 | /**
18 | * @callback EsbuildOverride
19 | * @param {EsbuildOption} option - Default option values to be used when remix calls esbuild
20 | * @param {EsbuildContext} context - @see {@link EsbuildContext}
21 | * @return {EsbuildOption} - Return the option values you override
22 | */
23 | export type EsbuildOverride = (
24 | option: EsbuildOption,
25 | context: EsbuildContext
26 | ) => EsbuildOption;
27 |
28 | let _esbuildOverride: EsbuildOverride = (arg) => arg;
29 |
30 | /**
31 | * This is a function to override esbuild; add it to remix.config.js.
32 | * @param {EsbuildOverride} esbuildOverride - callback function
33 | */
34 | export const withEsbuildOverride = (esbuildOverride?: EsbuildOverride) => {
35 | if (typeof esbuildOverride !== "function") {
36 | console.warn(
37 | "💽 esbuild is not overridden because no callback function is defined"
38 | );
39 | return;
40 | }
41 | _esbuildOverride = esbuildOverride;
42 |
43 | for (const mod of mods) {
44 | const esbuild = load<{
45 | overridden?: boolean;
46 | build: typeof build;
47 | context: typeof context;
48 | }>(mod);
49 | if (!esbuild) continue;
50 |
51 | if (esbuild.overridden) break;
52 | const originalBuildFunction = esbuild.build;
53 | const originalContextFunction = esbuild.context;
54 | try {
55 | Object.defineProperty(esbuild, "build", {
56 | get: () => (option: EsbuildOption) => {
57 | return originalBuildFunction(esbuildOverrideOption(option));
58 | },
59 | enumerable: true,
60 | });
61 | Object.defineProperty(esbuild, "context", {
62 | get: () => (option: EsbuildOption) => {
63 | return originalContextFunction(esbuildOverrideOption(option));
64 | },
65 | enumerable: true,
66 | });
67 | Object.defineProperty(esbuild, "overridden", {
68 | value: true,
69 | enumerable: true,
70 | });
71 | } catch {
72 | const msg =
73 | "❌ Override of esbuild failed. Check if postinstall has remix-esbuild-override set. See: https://github.com/aiji42/remix-esbuild-override#install";
74 | console.error(msg);
75 | throw new Error(msg);
76 | }
77 | console.log(
78 | "💽 Override esbuild. Your custom config can be used to build for Remix."
79 | );
80 | break;
81 | }
82 | };
83 |
84 | export const esbuildOverrideOption = (option: EsbuildOption) => {
85 | const isServer = option.write === false;
86 | const isDev = option.define?.["process.env.NODE_ENV"] === "development";
87 | const newOption = _esbuildOverride(option, { isServer, isDev });
88 | if (!newOption) {
89 | throw new Error(
90 | "❌ The callback function withEsbuildOverride must return the esbuild option value."
91 | );
92 | }
93 | return newOption;
94 | };
95 |
--------------------------------------------------------------------------------
/src/patching.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import { resolve } from "./utils";
3 | import { mods, defProPattern, defPropRedefine } from "./constants";
4 |
5 | export const patching = () => {
6 | for (const mod of mods) {
7 | const path = resolve(mod);
8 | if (!path) continue;
9 |
10 | const original = fs.readFileSync(path, { encoding: "utf8" });
11 | if (original.includes(defPropRedefine)) {
12 | console.log("💽 esbuild patch by remix-esbuild-override is complete.");
13 | break;
14 | }
15 | if (!original.match(defProPattern))
16 | throw new Error(
17 | "❌ esbuild patch by remix-esbuild-override failed, please check the the esbuild and remix versions and report this in a new issue. https://github.com/aiji42/remix-esbuild-override/issues/new"
18 | );
19 | const patched = original.replace(defProPattern, `$&\n${defPropRedefine}`);
20 | fs.writeFileSync(path, patched);
21 | console.log("💽 esbuild patch by remix-esbuild-override is complete.");
22 | break;
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | export const resolve = (mod: string): string | null => {
2 | try {
3 | return require.resolve(mod);
4 | } catch (_) {
5 | return null;
6 | }
7 | };
8 |
9 | export const load = (mod: string): T | null => {
10 | try {
11 | return require(mod) as T;
12 | } catch (_) {
13 | return null;
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "strict": true,
6 | "outDir": "dist",
7 | "declaration": true,
8 | "esModuleInterop": true,
9 | "skipLibCheck": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "types": ["vitest/globals"],
12 | "removeComments": false
13 | },
14 | "include": ["./src"],
15 | "exclude": [
16 | "./src/__tests__"
17 | ]
18 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { defineConfig } from "vite";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | test: {
8 | globals: true,
9 | coverage: {
10 | reporter: ["text", "json", "html", "lcov"],
11 | },
12 | },
13 | });
14 |
--------------------------------------------------------------------------------