Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix magic export deprecation messaging #4661

Merged
merged 6 commits into from
Nov 21, 2022

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented Nov 21, 2022

This reverts #3284 due to a few unforeseen issues:

  • The concatenating of files resulted in duplicate definitions of const getDeprecatedMessage and const warn which caused build failures
  • The import and actual usage of things in the ESM file broke the ability to tree-shake server-only code out of client bundles:
// Tree shakes correctly as long as things aren't used
export { createCookie } from '@remix-run/node`;

// Doesn't tree shake because `createCookie` is imported and referenced
import * as node from '@remix-run/node';
const createCookie = warn(node.createCookie, ...);

Instead this goes back to the old direct-export approach for the CJS/ESM modules, but keeps the annotated TS @deprecated exports. Then we added a new esbuild plugin that detects imports from remix at build time and logs out a warning for the problematic file:

Screenshot 2022-11-21 at 3 07 10 PM

Testing

  • Start a fresh remix app
  • Add a deprecated import in one of your routes
    • import { Link } from "remix"
  • Do a local remix build from this branch and direct the output to your new app:
    • REMIX_LOCAL_BUILD_DIRECTORY=../playgrounds/my-remix-app yarn build
  • Re-run the setup command in your Remix app directory:
    • npx remix setup node
  • npm run dev should show you a build time warning for the deprecated import
  • TS should show a deprecation error in VScode for the problematic import

@changeset-bot
Copy link

changeset-bot bot commented Nov 21, 2022

🦋 Changeset detected

Latest commit: 72a22c9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
remix Patch
@remix-run/dev Patch
create-remix Patch
@remix-run/architect Patch
@remix-run/cloudflare Patch
@remix-run/cloudflare-pages Patch
@remix-run/cloudflare-workers Patch
@remix-run/deno Patch
@remix-run/eslint-config Patch
@remix-run/express Patch
@remix-run/netlify Patch
@remix-run/node Patch
@remix-run/react Patch
@remix-run/serve Patch
@remix-run/server-runtime Patch
@remix-run/vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@MichaelDeBoey
Copy link
Member

MichaelDeBoey commented Nov 21, 2022

It would be a shame to revert #3284 imo, as we would lose TS deprecations that way, which are another added benefit imo.

Since this only seems to be impacting ESM builds, can we at least keep CJS & TS?


  • The concatenating of files resulted in duplicate definitions of const getDeprecatedMessage and const warn which caused build failures

This could be fixed by removing it from the specific magicExports file & including it in the concatenated file or we could rename these functions to getDeprecatedMessage_[package-name] & warn_[package-name] to not have duplicate function names 🤔


  • The import and actual usage of things in the ESM file broke the ability to tree-shake server-only code out of client bundles:
// Tree shakes correctly as long as things aren't used
export { createCookie } from '@remix-run/node`;

// Doesn't tree shake because `createCookie` is imported and referenced
import * as node from '@remix-run/node';
const createCookie = warn(node.createCookie, ...);

So if we can update the ESM imports properly, we can keep these I guess?

@brophdawg11 What if we do the following instead?

import { ${symbol} as ${moduleName}_${symbol} } from '${packageName}';\n
/** @deprecated Import `${symbol}` from `${packageName}` instead. *\n
const ${symbol} = warn(${moduleName}_${symbol}, getDeprecatedMessage('${symbol}', '${packageName}'));\n

So that we have

import { createCookie as node_createCookie } from '@remix-run/node';
/** @deprecated Import `createCookie` from `@remix-run/node` instead. */
const createCookie = warn(node_createCookie, getDeprecatedMessage('createCookie', '@remix-run/node'));

I'm happy to create a PR with the changes I suggested.

@brophdawg11
Copy link
Contributor Author

@MichaelDeBoey Definitely open to suggestions, this was just the quick fix towards getting 1.8.0-pre.1 out for community testing.

I like the idea of keeping the TS deprecation warnings.

I'm not sure if this would work for ESM, since the import is used and therefore I don't think qualifies for tree-shaking:

import { createCookie as node_createCookie } from '@remix-run/node';
/** @deprecated Import `createCookie` from `@remix-run/node` instead. */
const createCookie = warn(node_createCookie, getDeprecatedMessage('createCookie', '@remix-run/node'));
//                        ^ by accessing the imported thing here, I don't think it'll be tree shaken

Let me see about keeping the types. I think I lean towards keeping the CJS/ESM approaches the same to avoid unintended behavioral differences, but curious what others think there too.

I did the _{packagename} suffix locally as well and it fixes the duplicate identifier issue, but we would also need to enhance the warn function since the react components aren't functions and instead react element objects, so they need to be exported as is instead of called as a function.

@MichaelDeBoey
Copy link
Member

MichaelDeBoey commented Nov 21, 2022

I'm not sure if this would work for ESM, since the import is used and therefore I don't think qualifies for tree-shaking:

import { createCookie as node_createCookie } from '@remix-run/node';
/** @deprecated Import `createCookie` from `@remix-run/node` instead. */
const createCookie = warn(node_createCookie, getDeprecatedMessage('createCookie', '@remix-run/node'));
//                        ^ by accessing the imported thing here, I don't think it'll be tree shaken

What about changing it to the following?

import node from '@remix-run/node';
/** @deprecated Import `createCookie` from `@remix-run/node` instead. */
const createCookie = warn(node.createCookie, getDeprecatedMessage('createCookie', '@remix-run/node'));

Let me see about keeping the types. I think I lean towards keeping the CJS/ESM approaches the same to avoid unintended behavioral differences, but curious what others think there too.

This would mean we would loose the benefit of the deprecated JSDoc on all these functions

we would also need to enhance the warn function since the react components aren't functions and instead react element objects, so they need to be exported as is instead of called as a function.

Since our React component are function components, it's basically the same as a normal function, no?
Also: the warn function is re-exporting the same functionality as the original function was doing.

@brophdawg11
Copy link
Contributor Author

@MichaelDeBoey As soon as we reference anything from @remix-run/node in any way in that bundle, other than a direct re-export, it's unable to be tree shaken and ends up in the client bundle - since the calling of warn is a side effect which breaks tree-shaking:

Screenshot 2022-11-21 at 2 54 25 PM

I added back in the TS deprecations in 5083780 👍

Then @jacob-ebey had a better idea of how we can do this warning at build-time via an esbuild plugin, rather than litter runtime server/browser logs with these warnings (see 229503e). This allows us to better point the user at the file with the problematic import.

I think the React component was potentially from Link being a React.forwardRef - the fn coming into warn was a react-looking object instead of a function in my testing.

Copy link
Member

@MichaelDeBoey MichaelDeBoey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though we would loose the extra JSDoc deprecation warnings in your IDE, if there's no other reason to make sure we can still three-shake in ESM, I like the idea of esbuild throwing a warning 👍

Comment on lines +209 to +217
let cjsModule = camelCase(packageName.slice("@remix-run/".length));
cjsContents += `var ${cjsModule} = require('${packageName}');\n`;
for (let symbol of magicExports.values) {
cjsContents +=
`Object.defineProperty(exports, '${symbol}', {\n` +
" enumerable: true,\n" +
` get: function () { return ${cjsModule}.${symbol}; }\n` +
"});\n";
}
Copy link
Member

@MichaelDeBoey MichaelDeBoey Nov 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep the JSDoc deprecation here as well to have an extra IDE warning?

Suggested change
let cjsModule = camelCase(packageName.slice("@remix-run/".length));
cjsContents += `var ${cjsModule} = require('${packageName}');\n`;
for (let symbol of magicExports.values) {
cjsContents +=
`Object.defineProperty(exports, '${symbol}', {\n` +
" enumerable: true,\n" +
` get: function () { return ${cjsModule}.${symbol}; }\n` +
"});\n";
}
cjsContents += `var ${moduleName} = require('${packageName}');\n`;
cjsContents += magicExports.values
.map(
(symbol) =>
`/** @deprecated Import \`${symbol}\` from \`${packageName}\` instead. */\n` +
`exports.${symbol} = ${symbol};\n`
)
.join("\n");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MichaelDeBoey I'm having trouble actually seeing this JSDoc warning anywhere in my VSCode IDE 🤔

If I create a fresh Remix JS project, VSCode still shows me the deprecation from our .d.ts file in node_modules/remix in my .jsx files:

Screenshot 2022-11-21 at 4 04 24 PM

If I build with this JSDoc comment included and I remove the .d.ts file, then I don't see the deprecation, and I just see the JSDoc from the internal definition of the method, not the JSDoc of the re-exported version from remix:

Screenshot 2022-11-21 at 3 55 34 PM

So for now I'm going to merge this without the CJS JSDoc updates since I can't quite prove that they're picked up. It does seem that even in non-TS projects, at least VScode will pick up our .d.ts deprecation warning.

If we can find specific cases where the JSDoc enhances the DX I'm all for a subsequent PR before we release 1.8.0 stable, but for now I want to get 1.8.0-pre.1 out so folks can help test the new @remix-run/router work inside of Remix 👍

@brophdawg11 brophdawg11 merged commit a5fb1f6 into release-next Nov 21, 2022
@brophdawg11 brophdawg11 deleted the brophdawg11/fix-magic-exports branch November 21, 2022 21:10
@MichaelDeBoey
Copy link
Member

MichaelDeBoey commented Nov 22, 2022

@brophdawg11 When doing the following:

  • Run

    npx [email protected] --template remix remix-test-magicExports

    Choosing all defaults, except for JavaScript

  • Run

    npm i -D [email protected]
    npx remix setup
  • Disable all ESLint extends

  • Go to node_modules/remix/dist/index.js

  • Replace

    Object.defineProperty(exports, 'createCookie', {
      enumerable: true,
      get: function () { return node.createCookie; }
    });

    with

    /** @deprecated Import `createCookie` from `@remix-run/node` instead. */
    exports.createCookie = node.createCookie;

    (which is what my last suggestion was doing)

  • Add the following line to index.jsx

    import {createCookie} from "remix";
    
    const cookieCreator = createCookie; // this is just so my IDE (WebStorm) isn't complaining about `createCookie` not being used

I get the deprecated warning:
Schermafbeelding 2022-11-22 om 22 46 42


If you want, I can create a PR against release-next to add my suggestions again.

kentcdodds pushed a commit that referenced this pull request Dec 15, 2022
* Revert "Deprecate all functions & types exported from `magicExports` (#3284)"

This reverts commit 848d8b0.

* Single deprecation warning per remix index file

* Revert "Single deprecation warning per remix index file"

This reverts commit 7774492.

* Add back in TS deprecation typings

* Warn on deprecated remix imports via esbuild plugin

* add chgangeset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants