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

core(build): add basic inline-fs plugin #13232

Merged
merged 2 commits into from
Oct 20, 2021
Merged

core(build): add basic inline-fs plugin #13232

merged 2 commits into from
Oct 20, 2021

Conversation

brendankenny
Copy link
Member

part of #13231

To start, it only supports absolute string paths in fs.readFileSync and fs.readdirSync. It's not hooked up to anything but its tests.

The approach is outlined in the code, but to recap:

  • scan code for fs methods with regex
  • parse only the expression at each found index
  • statically evaluate arguments to fs method, collapsing to single string currently only allow string literals
  • execute fs method with computed argument
  • replace original expression with result of fs call
  • if an expression cannot be parsed or statically evaluated, warn and skip
  • if no expressions found or all are skipped, return null

next will be more supported argument types, structured warnings, source maps, replacing the actual plugins, etc

@brendankenny brendankenny requested a review from a team as a code owner October 20, 2021 00:03
@brendankenny brendankenny requested review from adamraine and removed request for a team October 20, 2021 00:03
@google-cla google-cla bot added the cla: yes label Oct 20, 2021
@connorjclark connorjclark requested review from connorjclark and removed request for adamraine October 20, 2021 18:31
Copy link
Collaborator

@connorjclark connorjclark left a comment

Choose a reason for hiding this comment

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

Great first pass! Most of the complexity coming up will be isolated to collapseToStringLiteral, which is nice.

Not many notes, just some nits.

re: "structured warnings", I'm not sure what you had in mind, but it'd be nice if some error in parsing (say, a failed assert) did not result in the entire thing failing.

To that end, I'd suggest:

  1. extracting the inner loop over foundIndices to a method and try/catching over it, collecting errors from parsing as they happen
  2. inlineFs could then return Promise<{code: string|null, warnings: string[]}> (or warnings?: string[] idk)

In other words, if we are going to return a list of warnings, we shouldn't also require the user to use try/catch.


const readContent = await fs.promises.readFile(constructedPath, 'utf8');

// TODO(bckenny): minify inlined javascript.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this part necessary? I think the subsequent steps of the bundler will handle the minification.

Copy link
Member Author

Choose a reason for hiding this comment

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

this is for the inlined string, which the minification at the bundler level will ignore as just a string literal. We're manually running the current minifyFileTransform rather than passing it in, more or less

Copy link
Collaborator

Choose a reason for hiding this comment

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

doh! of course. sgtm

expect(code).toBe(`const myTextContent = "template literal text content";`);
});

it('warns and skips unsupported syntax but inlines subsequent fs method calls', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This test asserts that the plugin won't attempt to replace the call using filePathVar, because it is not a string literal / is literally undefined here. When the plugin is changed to support statically-defined variables, this test case will then lose a little bit of clarity.

How about changing to something 100% invalid syntax, to explicitly test the robustness of the plugin? like

const myContent = fs.readFileSync(...!..., 'utf8');\nconst replacedContent = fs.readFileSync('${tmpPath}', 'utf8');

(or use some sufficiently hot-off-the-press JS language proposal that acorn does not yet support)

Could also add as a new test case, and just change the name of this test case to be "warns and skips fs method calls using unresolvable variables"

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, I think maybe I meant "unsupported construct" or something? Really the specific error was less important than the fact that it recovers from an fs call it can't inline and keeps looking for more. I'll rename the test to be clearer.

(I also have a real unsupported syntax test for when collapseToStringLiteral is filled in to catch that case)

expect(code).toBe(`const myContent = fs.readFileSync(filePathVar, 'utf8');\nconst replacedContent = "template literal text content";`);
});

// TODO: this will work
Copy link
Collaborator

Choose a reason for hiding this comment

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

meant to leave this comment?

Copy link
Member Author

Choose a reason for hiding this comment

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

meant to leave this comment?

yeah, just a note that it's not able to handle it now but will replace __dirname when the full thing is implemented

/**
* @fileoverview Types to provide basic `acorn` coverage, mainly to open up full
* ESTree AST types for the subset of acorn functionality we need.
* See https://github.com/acornjs/acorn/issues/946
Copy link
Collaborator

Choose a reason for hiding this comment

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

using the package from DefinitelyTyped is not sufficient?

Copy link
Member Author

Choose a reason for hiding this comment

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

using the package from DefinitelyTyped is not sufficient?

No, see that thread. The shipped acorn.d.ts takes precedence over the definitely typed version. However I think it's feasible to get rid of this file in exchange for two @ts-expect-errors in the parseExpressionAt function, so that's probably worth it

const code = await inlineFs(content);
// eslint-disable-next-line max-len
expect(code).toBe(`const myContent = fs.readFileSync(filePathVar, 'utf8');\nconst replacedContent = "template literal text content";`);
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

add a TODO to check for a warning (for once you've added that)

@brendankenny
Copy link
Member Author

re: "structured warnings", I'm not sure what you had in mind, but it'd be nice if some error in parsing (say, a failed assert) did not result in the entire thing failing.

yes, just what I was thinking :) I was going to use more or less esbuild's version because they're nice and if we decide someday to switch... :)

const parser = new acorn.Parser(options, input, offset);
// @ts-expect-error - Not part of the current acorn types.
parser.nextToken();
// @ts-expect-error - Not part of the current acorn types.
Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, these aren't part of the current acorn.d.ts, but between the shipped types and using the estree Node type we don't have to maintain our own type file, which seems like a big win

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.

4 participants