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

Replace "rollup" bundling with TypeScript project references #6492

Merged
merged 49 commits into from
Feb 26, 2024

Conversation

kraenhansen
Copy link
Member

@kraenhansen kraenhansen commented Feb 16, 2024

What, How & Why?

This is another stab at fixing #5894:

  • Deleted a usage of Rollup and related files in the SDK package.
  • Renamed the bundle NPM script in the SDK to build:ts, since it's no longer bundling.
  • Introduced separate tsconfig files for each runtime environment (Node.js, React-Native) + for tests and generating public types.
  • Introduced injection of the binding in platform/*/binding.ts. This was handled by Rollup before, but its not possible for the shared code to depend on the platform specific bindings, just based on file-locations. I experimented with using a self-referencing subpath export, but learned this was not fully supported by Metro. I believe this approach of injecting the binding can eventually help us to avoid exporting a top-level await on our WASM endpoint.
  • Removed all the named exports from ./index.ts since they we're not actually accessible externally anyway (we're using exports = Realm). I hope to be able to introduce these in a follow-up PR adding experimental ESM support for the package 🤞
  • The binding generator generates native.node.mjs and native.react-native.mjs, which are both ESM modules, but we need to be able to load them from the CJS SDK, so I've added a bindgen:transpile NPM script (dependent on by build:ts), which invoke babel to transform ESM to CJS in these files.
  • Rollup was generally happy to consume ESM or CJS, it didn't really care about file extensions etc, as all internal module references were removed during bundling anyway. I've spent a lot of time diving into TS modules, their use and limitations. Consider reading their theory for a more in-depth understanding.

If merged, I believe we can close #5853 as well.

Considerations

No longer including @realm/fetch

Since we're no longer bundling, there's no easy way to include the @realm/fetch code with the package.
We can probably just start publishing it to NPM, but it does add complexity and the risk of us forgetting to publish it or update the version in the SDK as it changes. If it worked, we might have included it as a bundled dependency. Perhaps we could do some symlink magic or find some other way to get it into the SDK package. Alternatively give in and use rollup, just for that. I haven't settled on this internally.

We're using stripInternal

We're relying on the stripInternal ts-config.
This was already the case with Rollup, so it's not a new problem introduced by this PR, but the rollup-plugin-dts plugin would perform a type-check, which we can no longer rely on. This makes us vulnerable to a situation where we accidentally forget an @internal annotation which relies on a type that is stripped from the package.

The use of the option has the following caveat in the docs:

This is an internal compiler option; use at your own risk, because the compiler does not check that the result is valid. If you are searching for a tool to handle additional levels of visibility within your d.ts files, look at api-extractor.

I spent quite some time trying to configure api-extractor, as recommended, but the use of self-referring subpath exports (which I was relying on at the time) is very confusing to the tool. It might be better now that I've reverted that and it's probably worth re-visiting.

For now, I've added a type-check NPM script, which builds the public types and tries building a project including them. This will fail if types are referenced which have been stripped from the public .d.ts files.

☑️ ToDos

  • 📝 Changelog entry
  • 📝 Compatibility label is updated or copied from previous entry
  • 📝 Update COMPATIBILITY.md
  • 🚦 Tests
  • 📦 Updated internal package version in consuming package.jsons (if updating internal packages)
  • 📱 Check the React Native/other sample apps work if necessary
  • 💥 Breaking label has been applied or is not necessary

@kraenhansen kraenhansen self-assigned this Feb 16, 2024
@cla-bot cla-bot bot added the cla: yes label Feb 16, 2024
@kraenhansen kraenhansen marked this pull request as draft February 21, 2024 22:12
Copy link
Contributor

@elle-j elle-j left a comment

Choose a reason for hiding this comment

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

I see a lot of work went into preparing and doing these great cleanups and refactoring! LGTM as soon as the tests are 🟢 👍 (plus comments)

@@ -17,6 +17,7 @@
////////////////////////////////////////////////////////////////////////////

import React, { createContext, useContext, useEffect, useState } from "react";
import Realm from "realm";
Copy link
Contributor

Choose a reason for hiding this comment

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

You may be able to only import the type here.

Suggested change
import Realm from "realm";
import type Realm from "realm";

Comment on lines 38 to 39
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function generate({ spec: boundSpec, file }: TemplateContext, out: Outputter): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function generate({ spec: boundSpec, file }: TemplateContext, out: Outputter): void {
export function generate({ spec: boundSpec }: TemplateContext, out: Outputter): void {

import { eslint } from "../eslint-formatter";
import { generate as generateBase, generateNativeBigIntSupport } from "./base-wrapper";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// eslint-disable-next-line @typescript-eslint/no-unused-vars

import { eslint } from "../eslint-formatter";
import { generate as generateBase, generateNativeBigIntSupport } from "./base-wrapper";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// eslint-disable-next-line @typescript-eslint/no-unused-vars

@@ -86,6 +82,8 @@
"build:ios:debug:simulator": "wireit",
"build:ios:debug:ios": "wireit",
"build:ios:debug:catalyst": "wireit",
"check-types": "wireit",
"clean": "git clean -fdx -e node_modules",
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this needed here as well? (In addition to the top-level file.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't remember why I choose to add it here as well, but I'll leave it out 👍

mongoClient(serviceName: string): MongoDB {
mongoClient(serviceName: string): MongoDBService {
Copy link
Contributor

Choose a reason for hiding this comment

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

Has the MongoDBService type been publicly exported with the same name as well?

Comment on lines +170 to +178
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.AppServicesFunction} */
export import RealmFunction = internal.AppServicesFunction;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.CanonicalPropertySchema} */
export import CanonicalObjectSchemaProperty = internal.CanonicalPropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.ClientResetRecoverUnsyncedChangesConfiguration} */
export import ClientResetRecoveryConfiguration = internal.ClientResetRecoverUnsyncedChangesConfiguration;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.PropertySchema} */
export import ObjectSchemaProperty = internal.PropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.RealmObjectConstructor} */
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.AppServicesFunction} */
export import RealmFunction = internal.AppServicesFunction;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.CanonicalPropertySchema} */
export import CanonicalObjectSchemaProperty = internal.CanonicalPropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.ClientResetRecoverUnsyncedChangesConfiguration} */
export import ClientResetRecoveryConfiguration = internal.ClientResetRecoverUnsyncedChangesConfiguration;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.PropertySchema} */
export import ObjectSchemaProperty = internal.PropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.RealmObjectConstructor} */
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.AppServicesFunction | AppServicesFunction} */
export import RealmFunction = internal.AppServicesFunction;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.CanonicalPropertySchema | CanonicalPropertySchema} */
export import CanonicalObjectSchemaProperty = internal.CanonicalPropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.ClientResetRecoverUnsyncedChangesConfiguration | ClientResetRecoverUnsyncedChangesConfiguration} */
export import ClientResetRecoveryConfiguration = internal.ClientResetRecoverUnsyncedChangesConfiguration;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.PropertySchema | PropertySchema} */
export import ObjectSchemaProperty = internal.PropertySchema;
/** @deprecated Will be removed in v13.0.0. Please use {@link internal.RealmObjectConstructor | RealmObjectConstructor} */


/**
* Applies SDK level patches to the binding.
* NOTE: This should only be called after the binding has been injected.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* NOTE: This should only be called after the binding has been injected.
* @note This should only be called after the binding has been injected.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can't find any documentation on this jsdoc / tsdoc tag 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it primarily makes it more visible (highlighted) that it's a note.

Comment on lines +24 to +25
import JsPlatformHelpers = binding.JsPlatformHelpers;
import Helpers = binding.Helpers;
Copy link
Contributor

Choose a reason for hiding this comment

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

I've read a bit about the special TS syntax, but for this case, I guess it didn't work to use const here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct - const only handles the value, whereas I need this to copy the namespace type as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay! 🙂

"rootDir": "src/platform/react-native",
"sourceMap": true,
"resolveJsonModule": true,
"noEmit": false,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the default value right? (P.S. the node ts config file did not explicitly specify it)

Copy link
Member Author

Choose a reason for hiding this comment

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

The react-native config that we extend sets this to true, so we need to override that here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay! 👍

Copy link
Contributor

@kneth kneth left a comment

Choose a reason for hiding this comment

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

I will have to read https://www.typescriptlang.org/docs/handbook/modules/theory.html carefully before my next round

@@ -2,7 +2,8 @@
"extends": "./tsconfig.node.base.json",
"compilerOptions": {
"outDir": "./dist/node-cjs",
"module": "CommonJS"
"module": "Node16",
Copy link
Contributor

Choose a reason for hiding this comment

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

According to the docs, it should be node16.

@@ -2,7 +2,8 @@
"extends": "./tsconfig.node.base.json",
"compilerOptions": {
"outDir": "./dist/node-esm",
"module": "ES2022"
"module": "es2022",
Copy link
Contributor

Choose a reason for hiding this comment

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

So you want to prepare for top-level awaits?

@@ -2,7 +2,8 @@
"extends": "./tsconfig.node.base.json",
"compilerOptions": {
"outDir": "./dist/node-esm",
"module": "ES2022"
"module": "es2022",
"moduleResolution": "Bundler"
Copy link
Contributor

Choose a reason for hiding this comment

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

According to the docs, it should be bundler

Copy link
Member Author

Choose a reason for hiding this comment

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

Either is fine, this was suggested by VSCode - it seems this property isn't case-sensitive.

@kraenhansen kraenhansen marked this pull request as ready for review February 24, 2024 13:43
@kraenhansen kraenhansen merged commit 7a06f6c into main Feb 26, 2024
31 checks passed
@kraenhansen kraenhansen deleted the kh/ts-project-references branch February 26, 2024 08:48
kraenhansen added a commit that referenced this pull request Mar 4, 2024
…to injection (#6524)

* Making path-browserify an actual dependency

* Adding a list of ignored props that can be accessed on the binding, before it's injected. Using Object.assign instead of replacing the injected.

* Applying patch before injecting

* Adding a note to the changelog

* Fixed the injection assertion
bimusiek pushed a commit to bimusiek/realm-js that referenced this pull request Mar 14, 2024
)

* Adding a "build" script to SDK using TS project references

* Removed "realm:bundle" in favour of "realm:build"

* Downgrading "module" of shared code to es2015

* Fixing up unit tests

* Exporting "bare" types from @realm/fetch

* Building only commonjs for now

* WIP

* No named exports only cjs

* Moving public types into dist

* Marking toFetchArgs as @internal

* Minor changes to platforms

* Stop generating TS via Cmake

* Seperating "node-wrapper" bindgen into two separate templates reusing a base

* Injecting binding instead of importing "realm/binding"

* Adding a script to clean the SDK package

* No need to emit the native.d.ts in the platform directories anymore

* Moved patching of binding to a  function called after injection

* Fixed relative paths in tsconfig.json

* Adding documentation on applyPatch

* Emitting source-maps

* Renaming "build" script to "build:typescript"

* Moved deprecated global Realm into its own file

* Refactored namespace API

* Renamed "build" to "build:typescript" across depending packages

* Expose Auth on the namespace

* Renamed "realm:build:typescript" to "realm:build:ts"

* Waypoint

* Adding a "check-types" script

* Adding @internal tags

* Exposing Realm on the Realm namespace to help ease ESM transition

* Adding a comment

* Removed the tsconfig.base.json

* Removed an extraneous internal tag

* Adding transpilation as a dep of "test"

* Fixed running tests and building from scratch

* Fixed tests (again)

* Fixed missing import in @realm/react

* Exporting the namespace deprecated-global.ts

* Fixed test coverage

* Removed the build:ts:instrumented script again

* Adding binding sourcecode to the dist artifact upload

* Fixing namespace errors

* Fixed lint error

* Adding a Jest wrapper for the RN entrypoint

* Attempt at fixing coverage

* Incorporating feedback

* Fixed tsconfigs

* Adding binding files to package files

* Fixed output of bindgen:generate:typescript
bimusiek pushed a commit to bimusiek/realm-js that referenced this pull request Mar 14, 2024
…rior to injection (realm#6524)

* Making path-browserify an actual dependency

* Adding a list of ignored props that can be accessed on the binding, before it's injected. Using Object.assign instead of replacing the injected.

* Applying patch before injecting

* Adding a note to the changelog

* Fixed the injection assertion
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants