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

feat: added extension-load-queue #8

Merged
merged 3 commits into from
Sep 3, 2024

Conversation

makhnatkin
Copy link
Contributor

@makhnatkin makhnatkin commented Sep 1, 2024

The mermaid, latex and HTML packages use the mechanism of asynchronous loading of runtime scripts. This process is implemented via a queue and by adding a field to the window object. This PR code is incorporated into a set of functions that can be used in any Diplodoc extension package.To protect against external access, the Symbol data type has been used instead of a string. The code has already been tested and debugged in the HTML extension

Because the .eslintrc.js file is generated using a pre-commit hook and the lint, typecheck and build scripts requires different TS configurations, a separate TS configuration file (tsconfig.build.json) has been created.

@makhnatkin makhnatkin marked this pull request as ready for review September 2, 2024 08:11
@makhnatkin makhnatkin self-assigned this Sep 2, 2024
@@ -3,6 +3,6 @@ module.exports = {
extends: require.resolve('@diplodoc/lint/eslint-config'),
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', './tsconfig.tests.json'],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code will be deleted every time by a pre-commit hook

Copy link
Member

Choose a reason for hiding this comment

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

yes, I will change it in @diplodoc/lint package.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this PR, I went another way - I created an alternative TS configuration for the build.

nodeExternalsPlugin(),
],
});

build({

Choose a reason for hiding this comment

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

TBH, I feel like making a clear distinction between react and non-react parts of code in this package is a particularly debatable decision. I'd vouch for just keeping it simple — that way, ELQ's react and non-react parts should probably reside in the same folder.

Also, while this is out of scope for this PR, I'd also argue that using ESBuild in the case of this particular package is also a pretty uncanny decision; in my opinion, it's better to ship all utility packages as a non-bundled set of TypeScript sources, tree shaking ought to do quite literally everything else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TBH, I feel like making a clear distinction between react and non-react parts of code in this package is a particularly debatable decision. I'd vouch for just keeping it simple — that way, ELQ's react and non-react parts should probably reside in the same folder.

I thought like this: hooks for React require the import of a React package. At the same time, the rest of the utilities seem to be more like a set of simple functions. I would like to be able to use non-React utilities separately. Additionally, it seems to me that other packages in diplodoc-platform are structured in a similar manner. But I don't have a strong opinion here.

Also, while this is out of scope for this PR, I'd also argue that using ESBuild in the case of this particular package is also a pretty uncanny decision; in my opinion, it's better to ship all utility packages as a non-bundled set of TypeScript sources, tree shaking ought to do quite literally everything else.

Interesting thoughts. I suggest solving them separately, not within the framework of this PR.


export type ControllerLoadedCallback<T> = (controller: T) => void;

export const QUEUE_SYMBOL: unique symbol = Symbol.for('queue');

Choose a reason for hiding this comment

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

No need to specify unique symbol explicitly, the Symbol.for statement is already inferred this way

@@ -0,0 +1,86 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type BrowserWindow = any;

Choose a reason for hiding this comment

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

I'd rather use declaration merging here in lieu of specifying any explicitly.

onQueueCreated?: (created: boolean) => void;
}

export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined';

Choose a reason for hiding this comment

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

As I imagine, isBrowser should be a very frequently used utility, so if we're at all serious about this package and its usage, i'd place this in a separate top-level file and would make sure to document this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, at the same time, the current version implies use in place - I'll leave it like that for now, or rename it. I'll also add a comment about correcting it in the future.


export const QUEUE_SYMBOL: unique symbol = Symbol.for('queue');

export type ScriptStore<T> = ControllerLoadedCallback<T>[] | null;

Choose a reason for hiding this comment

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

Wouldn't throwing an error ("This should only be used client-side") make more sense here though? In particular, it would allow to keep the API a bit more consistent without that null

if (store) {
store.push(setController);

return () => {

Choose a reason for hiding this comment

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

Honestly, I feel like it might be a better idea to auto-cleanup already fulfilled controller requests when processing the queue. Up for debate, though.

Copy link
Contributor Author

@makhnatkin makhnatkin Sep 2, 2024

Choose a reason for hiding this comment

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

Up for debate, though.

That's right. For now, I just want to put the general code into the utils.

.eslintignore Outdated
@@ -5,6 +5,7 @@
.DS_Store
node_modules
/lib
/react

Choose a reason for hiding this comment

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

This is extra at this point

declare global {
interface Window {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: symbol]: any;

Choose a reason for hiding this comment

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

This is still sub-optimal, even though I won't mind as much if you decide to leave this as is. An optimal typing strategy would be something like https://github.com/diplodoc-platform/html-extension/blob/dea1790907586c976d0ec3cf1a7f8dae7f1859ea/src/types.ts#L28

Copy link
Contributor Author

@makhnatkin makhnatkin Sep 3, 2024

Choose a reason for hiding this comment

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

In the example given, GLOBAL_SYMBOL is a pre-known constant. In mine, it is a key that is not known in advance. For the same reason, ScriptStore is generic in this PR. I propose to solve the problem of improved typing through future PR.


export const getQueueStore = () => {
if (isBrowser()) {
(window as Window)[QUEUE_SYMBOL] = (window as Window)[QUEUE_SYMBOL] || false;

Choose a reason for hiding this comment

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

as Window casts are weird — did you encounter any issues with declaration merging?

Copy link
Contributor Author

@makhnatkin makhnatkin Sep 3, 2024

Choose a reason for hiding this comment

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

Yes, unfortunately, I still face a set of problems. I can't get rid of this cast. I propose to solve the problem of improved typing through future PR.

@@ -0,0 +1,24 @@
import {AttrsParser} from './attrs';

Choose a reason for hiding this comment

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

Feels like this file and extension-load-queue.ts should be put in their own respective directory.
Then you could use export * from './extension-load-queue'; in this file, which would be much tidier if more utilities are added.

@makhnatkin makhnatkin merged commit f790e6a into master Sep 3, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants