-
Notifications
You must be signed in to change notification settings - Fork 202
Bundle extension files using esbuild #1483
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
Conversation
This bundles the extension files using esbuild, as recommended by VSCode. This reduces the size of the extension from 17MB to 6MB. Gulp will still run TypeScript to check types, but will not use the TypeScript compiler output in the bundle. See: https://code.visualstudio.com/api/working-with-extensions/bundling-extension
This disables the bundling of node_modules for the vscode-tests. This makes the test independent of each other but doesn't yet resolve all issues: proxyquire doesn't work and it's not possible to override some properties.
If the TypeScript output is no longer being bundled, I assume that the bundled extension winds up using |
extensions/ql-vscode/package.json
Outdated
"__metadata": { | ||
"id": "56d1e1f8-892a-4abc-b402-c473b832e324", | ||
"publisherDisplayName": "GitHub", | ||
"publisherId": "7c1c19cd-78eb-4dfb-8999-99caf7679002", | ||
"isPreReleaseVersion": false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks generated. Is this required?
I have the same question...also, the extension is currently using |
I'm sorry about the confusion, the PR body is not clear about this. The TypeScript files are still being compiled to JavaScript, but it's not using the TypeScript compiler anymore. ESBuild will take in the TypeScript files, strip the types, bundle it and create a .js and .js.map file. Previously, the TypeScript compiler would create a In comparison, ESBuild will take in all the files in the extension and all In terms of debugging, there will be no real difference since ESBuild will still generate a source map, so VSCode will automatically map the compiled bundle file to the original source paths (it might be using Overall, this reduces the number of files from thousands (I believe 7,000+) down to 1 file which needs to be loaded by VSCode (or could potentially be loaded). In the current state of this PR, we are only including the Unfortunately, we can't completely remove the |
Great, this is what I was hoping for. |
Nice. Thanks for the explanation. This makes sense. Looks like there's a failing build step for injecting the Application Insights key (for telemetry). The file that the script is looking for no longer exists since all files are concatenated, that step will need to change. The failure is here:
I think it would be sufficient to just change that to |
Unfortunately, it's not easily possible to use ESBuild for the test setup. There are two reasons for this: 1. We can't use the bundled extension for the tests; the tests depend on multiple files existing and that the extension requires the files and modules (e.g. for proxyquire). 2. ESBuild compiles to ES6 modules which have additional restrictions on what can be modified. Therefore, it's not possible to set certain exported properties to mock them. The alternative implemented in this commit is to use a separate test directory for the tests. This directory is not bundled with the extension. Instead, this directory is only used for running the tests and contains the TypeScript compiler output instead of the bundled extension.
I've now got all the tests to work, but there is now quite a large difference between the tests and development/production. For development/production, the approach is as described as above: 1 bundled file is created which contains all extension files and required modules. However, for the tests a completely different structure is used to facilitate mocking. If we use a bundled extension, we cannot (or only in a hacky way) access anything inside the extension. That would make it impossible to mock certain functions, which the test suite now uses quite extensively. Therefore, the extension is essentially build twice:
The differences between these environments is quite large (in terms of disk structure/how all modules interoperate), but they should function very similarly in terms of runtime behavior. There is still no difference between development/production, only between development/production and testing. @dbartol @aeisenberg What are your thoughts on this? This is essentially the way that VSCode recommends setting up the tests, but we also need access to the internals of the extension, so we need two completely different builds. It still reduces the size of the extension from 23MB to 4.4MB (a reduction of just over 80%) and should also improve performance, especially on systems where IO is slow (I can image that IO is quite a bit slower on Windows). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little concerned about using a vastly difference structure for running tests and for what we distribute. Doing so may mean that we don't catch extension packaging issues unless we test manually.
I mean...we should be testing manually anyway before a release because we have had other packaging issues. But these changes seem like it would be more likely for packaging bugs to slip through.
I think this change is very valuable since it helps make our published extension smaller and have fewer files. But I also think it is very valuable to (as much as possible) test what we ship. Can you be more explicit about what needs to change in order to run the tests from the esbuild extension?
My understanding is that the following is broken:
- proxyquire
- loading external test data
Anything else?
@@ -11,7 +11,7 @@ export function injectAppInsightsKey() { | |||
} | |||
|
|||
// replace the key | |||
return gulp.src(['out/telemetry.js']) | |||
return gulp.src(['out/extension.js']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tested this myself. I would just like to make sure that you have verified that the APP_INSIGHTS_KEY
env var is injected into the extension.js
file appropriately.
|
||
function cleanTestsOutput() { | ||
// The path to the test dist dir is outside of the working directory, so we need to force it | ||
return del(testDistDir, { force: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is del
async?
return tsProject.src() | ||
.pipe(tsProject(goodReporter())); | ||
return tsProject.src(); | ||
// .pipe(tsProject(goodReporter())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete?
@@ -93,9 +94,9 @@ describe('queryResolver', () => { | |||
getPrimaryDbschemeSpy = sinon.stub(); | |||
return proxyquire('../../../contextual/queryResolver', { | |||
'fs-extra': { | |||
ensureDirSync, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On line 9, there is:
const proxyquire = pq.noPreserveCache().noCallThru();
I don't think noCallThru
is necessary. If you remove that, do you still need to have this line?
@@ -1,6 +1,6 @@ | |||
import * as path from 'path'; | |||
import * as chai from 'chai'; | |||
import chaiAsPromised from 'chai-as-promised'; | |||
import * as chaiAsPromised from 'chai-as-promised'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this line and line 7 are not actually needed since chaiAsPromised
is configured in extensions/ql-vscode/src/vscode-tests/no-workspace/index.ts
.
In general the problem is that we cannot access anything from the extension itself when running bundled tests. That also means that when a test exists to test it in the environment of VSCode, a different function will be tested than is actually loaded in the extension. For example, if we call Loading external test data is actually fine since that doesn't involve us accessing the extension. Proxyquire is indeed unusable because when running a bundled test suite we're not loading any external modules. I think it will be quite a challenge to solve this problem using our current test suite. The only safe way to test the extension is to call commands and retrieve the output from somewhere in VSCode. It is not safe to call a function inside of the extension. Some of our existing tests can be moved to the unit test suite which should not impact the actual functionality testing (I think the |
Thanks for the explanation. That is indeed unfortunate. My concern is that we wind up testing something that is not being shipped and therefore miss something. I am not sure how likely this really is and I don't want to block what seems like a legitimate improvement in the extension. Most likely, any problem that the automated tests miss would be around some sort of packaging and distribution change. If we were to commit to more manual (smoke) testing, or some level of end to end testing before releasing, I think this change would be fine. |
Closing this in favour of #1799 |
This bundles the extension files using esbuild, as recommended by VSCode. This reduces the size of the extension from 17MB to 6MB.
Gulp will still run TypeScript to check types, but will not use the TypeScript compiler output in the bundle.
See: https://code.visualstudio.com/api/working-with-extensions/bundling-extension
TODO
mocha
.Checklist
ready-for-doc-review
label there.