esm - better content mapper to address sourcemaps (#230476)

This commit is contained in:
Benjamin Pasero 2024-10-04 15:34:21 +02:00 committed by GitHub
parent fe391be9f0
commit 9bd60d090a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 65 additions and 113 deletions

View File

@ -138,7 +138,16 @@ steps:
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map
displayName: Upload sourcemaps (Web)
displayName: Upload sourcemaps (Web Main)
- script: |
set -e
AZURE_STORAGE_ACCOUNT="vscodeweb" \
AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \
AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \
AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \
node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map
displayName: Upload sourcemaps (Web Internal)
# upload only the workbench.web.main.js source maps because
# we just compiled these bits in the previous step and the

View File

@ -85,77 +85,31 @@ const vscodeWebEntryPoints = [
buildfile.entrypoint('vs/workbench/workbench.web.main.internal') // TODO@esm remove line when we stop supporting web-amd-esm-bridge
].flat();
/**
* @param {object} product The parsed product.json file contents
*/
const createVSCodeWebProductConfigurationPatcher = (product) => {
/**
* @param content {string} The contents of the file
* @param path {string} The absolute file path, always using `/`, even on Windows
*/
const result = (content, path) => {
// (1) Patch product configuration
if (path.endsWith('vs/platform/product/common/product.js')) {
const productConfiguration = JSON.stringify({
...product,
version,
commit,
date: readISODate('out-build')
});
return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/);
}
return content;
};
return result;
};
/**
* @param extensionsRoot {string} The location where extension will be read from
*/
const createVSCodeWebBuiltinExtensionsPatcher = (extensionsRoot) => {
/**
* @param content {string} The contents of the file
* @param path {string} The absolute file path, always using `/`, even on Windows
*/
const result = (content, path) => {
// (2) Patch builtin extensions
if (path.endsWith('vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.js')) {
const builtinExtensions = JSON.stringify(extensions.scanBuiltinExtensions(extensionsRoot));
return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/);
}
return content;
};
return result;
};
/**
* @param patchers {((content:string, path: string)=>string)[]}
*/
const combineContentPatchers = (...patchers) => {
/**
* @param content {string} The contents of the file
* @param path {string} The absolute file path, always using `/`, even on Windows
*/
const result = (content, path) => {
for (const patcher of patchers) {
content = patcher(content, path);
}
return content;
};
return result;
};
/**
* @param extensionsRoot {string} The location where extension will be read from
* @param {object} product The parsed product.json file contents
*/
const createVSCodeWebFileContentMapper = (extensionsRoot, product) => {
return combineContentPatchers(
createVSCodeWebProductConfigurationPatcher(product),
createVSCodeWebBuiltinExtensionsPatcher(extensionsRoot)
);
return path => {
if (path.endsWith('vs/platform/product/common/product.js')) {
return content => {
const productConfiguration = JSON.stringify({
...product,
version,
commit,
date: readISODate('out-build')
});
return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/);
};
} else if (path.endsWith('vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.js')) {
return content => {
const builtinExtensions = JSON.stringify(extensions.scanBuiltinExtensions(extensionsRoot));
return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/);
};
}
return undefined;
};
};
exports.createVSCodeWebFileContentMapper = createVSCodeWebFileContentMapper;

View File

@ -53,18 +53,24 @@ function optimizeESMTask(opts) {
};
const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');
banner.js += await fs.promises.readFile(tslibPath, 'utf-8');
const boilerplateTrimmer = {
name: 'boilerplate-trimmer',
const contentsMapper = {
name: 'contents-mapper',
setup(build) {
build.onLoad({ filter: /\.js$/ }, async (args) => {
const contents = await fs.promises.readFile(args.path, 'utf-8');
const newContents = bundle.removeAllTSBoilerplate(contents);
build.onLoad({ filter: /\.js$/ }, async ({ path }) => {
// TS Boilerplate
const contents = await fs.promises.readFile(path, 'utf-8');
let newContents = bundle.removeAllTSBoilerplate(contents);
// File Content Mapper
const mapper = opts.fileContentMapper?.(path);
if (mapper) {
newContents = mapper(newContents);
}
return { contents: newContents };
});
}
};
const overrideExternalPlugin = {
name: 'override-external',
const externalOverride = {
name: 'external-override',
setup(build) {
// We inline selected modules that are we depend on on startup without
// a conditional `await import(...)` by hooking into the resolution.
@ -80,7 +86,7 @@ function optimizeESMTask(opts) {
platform: 'neutral', // makes esm
format: 'esm',
sourcemap: 'external',
plugins: [boilerplateTrimmer, overrideExternalPlugin],
plugins: [contentsMapper, externalOverride],
target: ['es2022'],
loader: {
'.ttf': 'file',
@ -101,23 +107,12 @@ function optimizeESMTask(opts) {
metafile: true, // enables res.metafile
}).then(res => {
for (const file of res.outputFiles) {
let contents = file.contents;
let sourceMapFile = undefined;
if (file.path.endsWith('.js')) {
if (opts.fileContentMapper) {
// UGLY the fileContentMapper is per file but at this point we have all files
// bundled already. So, we call the mapper for the same contents but each file
// that has been included in the bundle...
let newText = file.text;
for (const input of Object.keys(res.metafile.inputs)) {
newText = opts.fileContentMapper(newText, input);
}
contents = Buffer.from(newText);
}
sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`);
}
const fileProps = {
contents: Buffer.from(contents),
contents: Buffer.from(file.contents),
sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps
path: file.path,
base: path.join(REPO_ROOT_PATH, opts.src)

View File

@ -32,11 +32,9 @@ export interface IOptimizeESMTaskOpts {
*/
resources?: string[];
/**
* File contents interceptor
* @param contents The contents of the file
* @param path The absolute file path, always using `/`, even on Windows
* File contents interceptor for a given path.
*/
fileContentMapper?: (contents: string, path: string) => string;
fileContentMapper?: (path: string) => ((contents: string) => string) | undefined;
}
const DEFAULT_FILE_HEADER = [
@ -83,19 +81,28 @@ function optimizeESMTask(opts: IOptimizeESMTaskOpts): NodeJS.ReadWriteStream {
const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');
banner.js += await fs.promises.readFile(tslibPath, 'utf-8');
const boilerplateTrimmer: esbuild.Plugin = {
name: 'boilerplate-trimmer',
const contentsMapper: esbuild.Plugin = {
name: 'contents-mapper',
setup(build) {
build.onLoad({ filter: /\.js$/ }, async args => {
const contents = await fs.promises.readFile(args.path, 'utf-8');
const newContents = bundle.removeAllTSBoilerplate(contents);
build.onLoad({ filter: /\.js$/ }, async ({ path }) => {
// TS Boilerplate
const contents = await fs.promises.readFile(path, 'utf-8');
let newContents = bundle.removeAllTSBoilerplate(contents);
// File Content Mapper
const mapper = opts.fileContentMapper?.(path);
if (mapper) {
newContents = mapper(newContents);
}
return { contents: newContents };
});
}
};
const overrideExternalPlugin: esbuild.Plugin = {
name: 'override-external',
const externalOverride: esbuild.Plugin = {
name: 'external-override',
setup(build) {
// We inline selected modules that are we depend on on startup without
// a conditional `await import(...)` by hooking into the resolution.
@ -112,7 +119,7 @@ function optimizeESMTask(opts: IOptimizeESMTaskOpts): NodeJS.ReadWriteStream {
platform: 'neutral', // makes esm
format: 'esm',
sourcemap: 'external',
plugins: [boilerplateTrimmer, overrideExternalPlugin],
plugins: [contentsMapper, externalOverride],
target: ['es2022'],
loader: {
'.ttf': 'file',
@ -135,27 +142,14 @@ function optimizeESMTask(opts: IOptimizeESMTaskOpts): NodeJS.ReadWriteStream {
}).then(res => {
for (const file of res.outputFiles) {
let contents = file.contents;
let sourceMapFile: esbuild.OutputFile | undefined = undefined;
if (file.path.endsWith('.js')) {
if (opts.fileContentMapper) {
// UGLY the fileContentMapper is per file but at this point we have all files
// bundled already. So, we call the mapper for the same contents but each file
// that has been included in the bundle...
let newText = file.text;
for (const input of Object.keys(res.metafile.inputs)) {
newText = opts.fileContentMapper(newText, input);
}
contents = Buffer.from(newText);
}
sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`);
}
const fileProps = {
contents: Buffer.from(contents),
contents: Buffer.from(file.contents),
sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps
path: file.path,
base: path.join(REPO_ROOT_PATH, opts.src)