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

refactor(webpack-cli): don't spawn needless shells and replace execa with cross-spawn #3146

Merged
merged 4 commits into from
Mar 8, 2022

Conversation

ludofischer
Copy link
Contributor

What kind of change does this PR introduce?
Refactor: use a simple child process instead of a shell and replace execa with cross-spawn.

Did you add tests for your changes?
No, I only replaced the execa mocks with cross-spawn mocks, as the functionality should be identical as before.
I've tested webpack init on my local machine.

Summary
Executing a shell is less performant, less secure and less cross-platform, according to both the execa and cross-spawn documentation.

Replacing execa with cross-spawn removes 9 dependencies on a production install:

  • execa
  • get-stream
  • human-signals
  • is-stream
  • mimic-fn
  • npm-run-path
  • onetime
  • signal-exit
  • strip-final-newline

I can't find any options unique to execa in the codebase and cross-spawn should take care of the Windows compatibility issues.

Does this PR introduce a breaking change?
No.

Other information
execa uses cross-spawn under the hood to parse the command arguments, so nothing should change in that regard: https://github.com/sindresorhus/execa/blob/main/index.js


try {
await execa(commandToBeRun, [], { stdio: "inherit", shell: true });
sync(packageManager, commandArguments, { stdio: "inherit" });
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can't understand why this is the only spot where sync was not used and shell was set to true. Could be that it was a way to make passing all the arguments in a single string work?

Copy link
Member

Choose a reason for hiding this comment

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

Good questions, maybe it was just mistake

Copy link
Member

@snitin315 snitin315 Feb 27, 2022

Choose a reason for hiding this comment

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

With execa wee need shell: true for running npm commands.

npm is not a binary but a shell file. npm requires using cmd.exe or Bash to be loaded. The shell option does that.

Refers sindresorhus/execa#433 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there also a reason the shell must run async? Intuitively it does not seem the case, as there is no way the program can continue if the install does not finish.

It's true that npm is a bash script on Unix and a cmd file on Windows.

But later on the comment you've linked adds

If you specify a shell file (as opposed to a binary), on Windows, and do not use the shell option, Execa emulates that option for you (through the dependency node-cross-spawn) by basically running cmd /d /s /c /q "npm run tsc" instead of npm run tsc

I think that must be true, because f the shell option is required, the previous execa calls, all without shell: true would fail and I think someone would have reported the bug. I think the shell: true is really needed if you want the shell to expand or replace something in the command string.

@@ -275,6 +268,10 @@ class WebpackCLI implements IWebpackCLI {
});
};

// yarn uses 'add' command, rest npm and pnpm both use 'install'
const commandArguments = [packageManager === "yarn" ? "add" : "install", "-D", packageName];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can re-use the argument array if we keep it separate instead of concatenating immediately into a single string.

@ludofischer ludofischer marked this pull request as ready for review February 25, 2022 21:20
@ludofischer ludofischer requested a review from a team as a code owner February 25, 2022 21:20
@ludofischer
Copy link
Contributor Author

I should have fixed the failing install tests now. I hope all tests pass on CI because I can't fix one test that's always failed for me locally (even on master). test/watch/analyze/analyze-flag.test.js reports:

● "analyze" option › should load webpack-bundle-analyzer plugin with --analyze flag

expect(received).toContain(expected) // indexOf

Expected substring: "Webpack Bundle Analyzer is started at"
Received string:    "Webpack Bundle Analyzer is started at http://127.0.0.1:8888
Use Ctrl+C to close it"

The string does contain the substring so I have little idea of what's wrong.

@ludofischer
Copy link
Contributor Author

The smoketests all pass on my machine, so I can't say why they fail now. They also passed on the previous CI run.

@alexander-akait
Copy link
Member

Can you try to update lock file?

@ludofischer
Copy link
Contributor Author

Can you try to update lock file?

I've updated all versions in yarn.lock.

@alexander-akait
Copy link
Member

Oh, looks like we should update types when we updating lock file

@ludofischer
Copy link
Contributor Author

Oh, looks like we should update types when we updating lock file

I think it's the opposite, updating @types/yeoman-environment from 2.1.0.5 to 2.10.6 breaks the type check. I've removed the commit that updated all dependencies and rebased, since some dependencies have been updated on master in the meantime.

@alexander-akait
Copy link
Member

hm, we have a problems with:

RUN  Missing webpack-dev-server
  swapping webpack-dev-server with .webpack-dev-server
  timeout: killing process
  swapping .webpack-dev-server with webpack-dev-server
FAIL  Missing webpack-dev-server

RUN  Missing webpack
  swapping webpack with .webpack
  timeout: killing process
  swapping .webpack with webpack
FAIL  Missing webpack

RUN  Missing webpack-bundle-analyzer
  swapping webpack-bundle-analyzer with .webpack-bundle-analyzer
  timeout: killing process
  swapping .webpack-bundle-analyzer with webpack-bundle-analyzer
FAIL  Missing webpack-bundle-analyzer

That is weird...

@ludofischer
Copy link
Contributor Author

ludofischer commented Mar 5, 2022

That is weird...

I think the problem is timeout: killing process, not the missing ... messages, because the missing messages also appear when the tests pass.
The smoketests all pass on my machine, even when I follow the exact same steps as in CI. I think the timeout comes from smoketests/helpers.js

console.log(" timeout: killing process");

but it's set to 30 seconds, which should be plenty (on my machine each tests runs in a second or so).

@alexander-akait
Copy link
Member

Can we try to increase timeout? Sorry for this, don't have time on deep investigation for this

Remove 9 dependencies on a production install:
- execa
- get-stream
- human-signals
- is-stream
- mimic-fn
- npm-run-path
- onetime
- signal-exit
- strip-final-newline
Execute install command synchronously instead of spawning a shell
asynchronously.
- The requested action cannot proceed until the required install completes,
  so I don't see an advantage to an asynchronous process
- Starting the subprocess directly should be more performant.
  It should also be more cross-plattform and secure, since it prevents
  interpolations and expansions.
Mock the `sync` property.
Split command into command name and argument array.
@ludofischer
Copy link
Contributor Author

Can we try to increase timeout? Sorry for this, don't have time on deep investigation for this

I've increased the timeout to 60 seconds.

@codecov
Copy link

codecov bot commented Mar 7, 2022

Codecov Report

Merging #3146 (db9c10c) into master (b1ad914) will decrease coverage by 0.05%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3146      +/-   ##
==========================================
- Coverage   91.50%   91.44%   -0.06%     
==========================================
  Files          23       23              
  Lines        1719     1719              
  Branches      519      519              
==========================================
- Hits         1573     1572       -1     
- Misses        146      147       +1     
Impacted Files Coverage Δ
packages/webpack-cli/src/webpack-cli.ts 93.95% <100.00%> (-0.10%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b1ad914...db9c10c. Read the comment docs.

@alexander-akait
Copy link
Member

Maybe we need increase some more

@ludofischer
Copy link
Contributor Author

Maybe we need increase some more

I'm not sure, that timeout only affects the smoketests but now it's the other tests hanging. Anyway, this time the smoketests passed in less than 1 min on Ubuntu, I think either the containers are too slow or there is some deadlock issue.

Copy link
Member

@alexander-akait alexander-akait left a comment

Choose a reason for hiding this comment

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

/cc @webpack/cli-team Let's review

Copy link
Member

@anshumanv anshumanv left a comment

Choose a reason for hiding this comment

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

Thanks

@snitin315 snitin315 merged commit ce74898 into webpack:master Mar 8, 2022
@ludofischer ludofischer deleted the remove-execa branch March 8, 2022 13:20
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.

6 participants