esm - introduce VSCODE_BUILD_ESM variable (#225586)

This commit is contained in:
Benjamin Pasero 2024-08-14 14:49:26 +02:00 committed by GitHub
parent 7f4969a41e
commit 0dea4804f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 960 additions and 57 deletions

View File

@ -30,7 +30,15 @@
**/src/vs/*/**/*.d.ts
**/src/vs/base/test/common/filters.perf.data.js
**/src/vs/loader.js
**/src2/**/dompurify.js
**/src2/**/marked.js
**/src2/**/semver.js
**/src2/typings/**/*.d.ts
**/src2/vs/*/**/*.d.ts
**/src2/vs/base/test/common/filters.perf.data.js
**/src2/vs/loader.js
**/test/unit/assert.js
**/test/unit/assert-esm.js
**/test/automation/out/**
**/typings/**
!.vscode

View File

@ -32,6 +32,7 @@
"src/vs/base/test/common/filters.perf.data.js": true,
"src/vs/base/test/node/uri.perf.data.txt": true,
"src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true,
"src/vs/base/test/node/uri.test.data.txt": true,
"src/vs/editor/test/node/diffing/fixtures/**": true,
},
"files.readonlyInclude": {
@ -50,6 +51,7 @@
"test/smoke/out/**": true,
"test/automation/out/**": true,
"test/integration/browser/out/**": true,
"src2/**": true,
},
"files.readonlyExclude": {
"build/builtin/*.js": true,

View File

@ -36,5 +36,23 @@
"private readonly _onDid$1 = new Emitter<$2>();",
"readonly onDid$1: Event<$2> = this._onDid$1.event;"
],
},
"esm-comment": {
"scope": "typescript,javascript",
"prefix": "esm-comment",
"body": [
"// ESM-comment-begin",
"$SELECTION$0",
"// ESM-comment-end",
]
},
"esm-uncomment": {
"scope": "typescript,javascript",
"prefix": "esm-uncomment",
"body": [
"// ESM-uncomment-begin",
"// $SELECTION$0",
"// ESM-uncomment-end",
]
}
}

View File

@ -100,6 +100,10 @@ parameters:
displayName: "Skip tests"
type: boolean
default: false
- name: VSCODE_BUILD_ESM # TODO@bpasero remove me once ESM is shipped
displayName: "️❗ Build as ESM (!FOR TESTING ONLY!) ️❗"
type: boolean
default: false
variables:
- name: VSCODE_PRIVATE_BUILD
@ -110,6 +114,8 @@ variables:
value: ${{ parameters.CARGO_REGISTRY }}
- name: VSCODE_QUALITY
value: ${{ parameters.VSCODE_QUALITY }}
- name: VSCODE_BUILD_ESM
value: ${{ parameters.VSCODE_BUILD_ESM }}
- name: VSCODE_BUILD_STAGE_WINDOWS
value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}
- name: VSCODE_BUILD_STAGE_LINUX
@ -217,6 +223,7 @@ extends:
- template: build/azure-pipelines/product-compile.yml@self
parameters:
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
VSCODE_BUILD_ESM: ${{ variables.VSCODE_BUILD_ESM }}
- ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}:
- stage: CompileCLI

View File

@ -1,6 +1,9 @@
parameters:
- name: VSCODE_QUALITY
type: string
- name: VSCODE_BUILD_ESM
type: boolean
default: false
steps:
- task: NodeTool@0
@ -98,6 +101,10 @@ steps:
- script: node build/azure-pipelines/distro/mixin-quality
displayName: Mixin distro quality
- ${{ if eq(parameters.VSCODE_BUILD_ESM, true) }}:
- script: node migrate.mjs --disable-watch
displayName: Migrate to ESM
- template: common/install-builtin-extensions.yml@self
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:

View File

@ -9,6 +9,7 @@ const fs = require("fs");
const minimatch = require("minimatch");
const vscode_universal_bundler_1 = require("vscode-universal-bundler");
const cross_spawn_promise_1 = require("@malept/cross-spawn-promise");
const esm_1 = require("../lib/esm");
const root = path.dirname(path.dirname(__dirname));
async function main(buildDir) {
const arch = process.env['VSCODE_ARCH'];
@ -26,13 +27,14 @@ async function main(buildDir) {
'**/CodeResources',
'**/Credits.rtf',
];
const canAsar = !(0, esm_1.isESM)('ASAR disabled in universal build'); // TODO@esm ASAR disabled in ESM
await (0, vscode_universal_bundler_1.makeUniversalApp)({
x64AppPath,
arm64AppPath,
asarPath: asarRelativePath,
asarPath: canAsar ? asarRelativePath : undefined,
outAppPath,
force: true,
mergeASARs: true,
mergeASARs: canAsar,
x64ArchFiles: '*/kerberos.node',
filesToSkipComparison: (file) => {
for (const expected of filesToSkip) {

View File

@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as minimatch from 'minimatch';
import { makeUniversalApp } from 'vscode-universal-bundler';
import { spawn } from '@malept/cross-spawn-promise';
import { isESM } from '../lib/esm';
const root = path.dirname(path.dirname(__dirname));
@ -31,13 +32,15 @@ async function main(buildDir?: string) {
'**/Credits.rtf',
];
const canAsar = !isESM('ASAR disabled in universal build'); // TODO@esm ASAR disabled in ESM
await makeUniversalApp({
x64AppPath,
arm64AppPath,
asarPath: asarRelativePath,
asarPath: canAsar ? asarRelativePath : undefined,
outAppPath,
force: true,
mergeASARs: true,
mergeASARs: canAsar,
x64ArchFiles: '*/kerberos.node',
filesToSkipComparison: (file: string) => {
for (const expected of filesToSkip) {

View File

@ -78,6 +78,7 @@ module.exports.indentationFilter = [
'!src/vs/base/node/terminateProcess.sh',
'!src/vs/base/node/cpuUsage.sh',
'!test/unit/assert.js',
'!test/unit/assert-esm.js',
'!resources/linux/snap/electron-launch',
'!build/ext.js',
'!build/npm/gyp/patches/gyp_spectre_mitigation_support.patch',

View File

@ -9,10 +9,13 @@
const gulp = require('gulp');
const util = require('./lib/util');
const date = require('./lib/date');
const esm = require('./lib/esm');
const task = require('./lib/task');
const compilation = require('./lib/compilation');
const optimize = require('./lib/optimize');
const isESMBuild = typeof process.env.VSCODE_BUILD_ESM === 'string' && process.env.VSCODE_BUILD_ESM.toLowerCase() === 'true';
/**
* @param {boolean} disableMangle
*/
@ -21,8 +24,9 @@ function makeCompileBuildTask(disableMangle) {
util.rimraf('out-build'),
util.buildWebNodePaths('out-build'),
date.writeISODate('out-build'),
esm.setESM(isESMBuild),
compilation.compileApiProposalNamesTask,
compilation.compileTask('src', 'out-build', true, { disableMangle }),
compilation.compileTask(isESMBuild ? 'src2' : 'src', 'out-build', true, { disableMangle }),
optimize.optimizeLoaderTask('out-build', 'out-build', true)
);
}

View File

@ -31,6 +31,7 @@ const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('
const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web');
const cp = require('child_process');
const log = require('fancy-log');
const { isESM } = require('./lib/esm');
const REPO_ROOT = path.dirname(__dirname);
const commit = getVersion(REPO_ROOT);
@ -299,7 +300,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
let packageJsonContents;
const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' })
.pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined }))
.pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, ...(isESM(`Setting 'type: module' in top level package.json`) ? { type: 'module' } : {}) })) // TODO@esm this should be configured in the top level package.json
.pipe(es.through(function (file) {
packageJsonContents = file.contents.toString();
this.emit('data', file);

View File

@ -33,6 +33,7 @@ const minimist = require('minimist');
const { compileBuildTask } = require('./gulpfile.compile');
const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions');
const { promisify } = require('util');
const { isESM } = require('./lib/esm');
const glob = promisify(require('glob'));
const rcedit = promisify(require('rcedit'));
@ -79,10 +80,11 @@ const vscodeResources = [
// Do not change the order of these files! They will
// be inlined into the target window file in this order
// and they depend on each other in this way.
const windowBootstrapFiles = [
'out-build/vs/loader.js',
'out-build/bootstrap-window.js'
];
const windowBootstrapFiles = [];
if (!isESM('Skipping loader.js in window bootstrap files')) {
windowBootstrapFiles.push('out-build/vs/loader.js');
}
windowBootstrapFiles.push('out-build/bootstrap-window.js');
const commonJSEntryPoints = [
'out-build/main.js',
@ -242,7 +244,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
}
const name = product.nameShort;
const packageJsonUpdates = { name, version };
const packageJsonUpdates = { name, version, ...(isESM(`Setting 'type: module' and 'main: out/main.js' in top level package.json`) ? { type: 'module', main: 'out/main.js' } : {}) }; // TODO@esm this should be configured in the top level package.json
// for linux url handling
if (platform === 'linux') {
@ -275,16 +277,18 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path));
const root = path.resolve(path.join(__dirname, '..'));
const productionDependencies = getProductionDependencies(root);
const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat();
const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!**/*.mk`]).flat();
const deps = gulp.src(dependenciesSrc, { base: '.', dot: true })
let deps = gulp.src(dependenciesSrc, { base: '.', dot: true })
.pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map']))
.pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore')))
.pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`)))
.pipe(jsFilter)
.pipe(util.rewriteSourceMappingURL(sourceMappingURLBase))
.pipe(jsFilter.restore)
.pipe(createAsar(path.join(process.cwd(), 'node_modules'), [
.pipe(jsFilter.restore);
if (!isESM('ASAR disabled in VS Code builds')) { // TODO@esm: ASAR disabled in ESM
deps = deps.pipe(createAsar(path.join(process.cwd(), 'node_modules'), [
'**/*.node',
'**/@vscode/ripgrep/bin/*',
'**/node-pty/build/Release/*',
@ -296,6 +300,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op
], [
'**/*.mk',
], 'node_modules.asar'));
}
let all = es.merge(
packageJsonStream,

View File

@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.bundle = bundle;
exports.removeDuplicateTSBoilerplate = removeDuplicateTSBoilerplate;
exports.removeAllTSBoilerplate = removeAllTSBoilerplate;
const fs = require("fs");
const path = require("path");
const vm = require("vm");
@ -226,20 +226,24 @@ function removeAllDuplicateTSBoilerplate(destFiles) {
});
return destFiles;
}
function removeAllTSBoilerplate(source) {
const seen = new Array(BOILERPLATE.length).fill(true, 0, 10);
return removeDuplicateTSBoilerplate(source, seen);
}
// Taken from typescript compiler => emitFiles
const BOILERPLATE = [
{ start: /^var __extends/, end: /^}\)\(\);$/ },
{ start: /^var __assign/, end: /^};$/ },
{ start: /^var __decorate/, end: /^};$/ },
{ start: /^var __metadata/, end: /^};$/ },
{ start: /^var __param/, end: /^};$/ },
{ start: /^var __awaiter/, end: /^};$/ },
{ start: /^var __generator/, end: /^};$/ },
{ start: /^var __createBinding/, end: /^}\)\);$/ },
{ start: /^var __setModuleDefault/, end: /^}\);$/ },
{ start: /^var __importStar/, end: /^};$/ },
];
function removeDuplicateTSBoilerplate(source, SEEN_BOILERPLATE = []) {
// Taken from typescript compiler => emitFiles
const BOILERPLATE = [
{ start: /^var __extends/, end: /^}\)\(\);$/ },
{ start: /^var __assign/, end: /^};$/ },
{ start: /^var __decorate/, end: /^};$/ },
{ start: /^var __metadata/, end: /^};$/ },
{ start: /^var __param/, end: /^};$/ },
{ start: /^var __awaiter/, end: /^};$/ },
{ start: /^var __generator/, end: /^};$/ },
{ start: /^var __createBinding/, end: /^}\)\);$/ },
{ start: /^var __setModuleDefault/, end: /^}\);$/ },
{ start: /^var __importStar/, end: /^};$/ },
];
const lines = source.split(/\r\n|\n|\r/);
const newLines = [];
let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE;

View File

@ -54,6 +54,7 @@ export interface IEntryPoint {
prepend?: IExtraFile[];
append?: IExtraFile[];
dest?: string;
target?: 'amd' | 'esm';
}
interface IEntryPointMap {
@ -361,22 +362,26 @@ function removeAllDuplicateTSBoilerplate(destFiles: IConcatFile[]): IConcatFile[
return destFiles;
}
export function removeDuplicateTSBoilerplate(source: string, SEEN_BOILERPLATE: boolean[] = []): string {
export function removeAllTSBoilerplate(source: string) {
const seen = new Array<boolean>(BOILERPLATE.length).fill(true, 0, 10);
return removeDuplicateTSBoilerplate(source, seen);
}
// Taken from typescript compiler => emitFiles
const BOILERPLATE = [
{ start: /^var __extends/, end: /^}\)\(\);$/ },
{ start: /^var __assign/, end: /^};$/ },
{ start: /^var __decorate/, end: /^};$/ },
{ start: /^var __metadata/, end: /^};$/ },
{ start: /^var __param/, end: /^};$/ },
{ start: /^var __awaiter/, end: /^};$/ },
{ start: /^var __generator/, end: /^};$/ },
{ start: /^var __createBinding/, end: /^}\)\);$/ },
{ start: /^var __setModuleDefault/, end: /^}\);$/ },
{ start: /^var __importStar/, end: /^};$/ },
];
// Taken from typescript compiler => emitFiles
const BOILERPLATE = [
{ start: /^var __extends/, end: /^}\)\(\);$/ },
{ start: /^var __assign/, end: /^};$/ },
{ start: /^var __decorate/, end: /^};$/ },
{ start: /^var __metadata/, end: /^};$/ },
{ start: /^var __param/, end: /^};$/ },
{ start: /^var __awaiter/, end: /^};$/ },
{ start: /^var __generator/, end: /^};$/ },
{ start: /^var __createBinding/, end: /^}\)\);$/ },
{ start: /^var __setModuleDefault/, end: /^}\);$/ },
{ start: /^var __importStar/, end: /^};$/ },
];
function removeDuplicateTSBoilerplate(source: string, SEEN_BOILERPLATE: boolean[] = []): string {
const lines = source.split(/\r\n|\n|\r/);
const newLines: string[] = [];
let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE: RegExp;

41
build/lib/esm.js Normal file
View File

@ -0,0 +1,41 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.setESM = setESM;
exports.isESM = isESM;
const path = require("path");
const fs = require("fs");
// TODO@esm remove this
const outDirectory = path.join(__dirname, '..', '..', 'out-build');
const esmMarkerFile = path.join(outDirectory, 'esm');
function setESM(enabled) {
const result = () => new Promise((resolve, _) => {
if (enabled) {
fs.mkdirSync(outDirectory, { recursive: true });
fs.writeFileSync(esmMarkerFile, 'true', 'utf8');
console.warn(`Setting build to ESM: true`);
}
else {
console.warn(`Setting build to ESM: false`);
}
resolve();
});
result.taskName = 'set-esm';
return result;
}
function isESM(logWarning) {
try {
const res = fs.readFileSync(esmMarkerFile, 'utf8') === 'true';
if (res && logWarning) {
console.warn(`ESM: ${logWarning}`);
}
return res;
}
catch (error) {
return false;
}
}
//# sourceMappingURL=esm.js.map

40
build/lib/esm.ts Normal file
View File

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as fs from 'fs';
// TODO@esm remove this
const outDirectory = path.join(__dirname, '..', '..', 'out-build');
const esmMarkerFile = path.join(outDirectory, 'esm');
export function setESM(enabled: boolean) {
const result = () => new Promise<void>((resolve, _) => {
if (enabled) {
fs.mkdirSync(outDirectory, { recursive: true });
fs.writeFileSync(esmMarkerFile, 'true', 'utf8');
console.warn(`Setting build to ESM: true`);
} else {
console.warn(`Setting build to ESM: false`);
}
resolve();
});
result.taskName = 'set-esm';
return result;
}
export function isESM(logWarning?: string): boolean {
try {
const res = fs.readFileSync(esmMarkerFile, 'utf8') === 'true';
if (res && logWarning) {
console.warn(`ESM: ${logWarning}`);
}
return res;
} catch (error) {
return false;
}
}

View File

@ -11,6 +11,7 @@ const File = require("vinyl");
const sm = require("source-map");
const path = require("path");
const sort = require("gulp-sort");
const esm_1 = require("./esm");
var CollectStepResult;
(function (CollectStepResult) {
CollectStepResult[CollectStepResult["Yes"] = 0] = "Yes";
@ -169,13 +170,23 @@ var _nls;
.filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration)
.map(n => n)
.filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference)
.filter(d => d.moduleReference.expression.getText() === '\'vs/nls\'');
.filter(d => {
if ((0, esm_1.isESM)()) {
return d.moduleReference.expression.getText().endsWith(`/nls.js'`);
}
return d.moduleReference.expression.getText() === '\'vs/nls\'';
});
// import ... from 'vs/nls';
const importDeclarations = imports
.filter(n => n.kind === ts.SyntaxKind.ImportDeclaration)
.map(n => n)
.filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral)
.filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'')
.filter(d => {
if ((0, esm_1.isESM)()) {
return d.moduleSpecifier.getText().endsWith(`/nls.js'`);
}
return d.moduleSpecifier.getText() === '\'vs/nls\'';
})
.filter(d => !!d.importClause && !!d.importClause.namedBindings);
// `nls.localize(...)` calls
const nlsLocalizeCallExpressions = importDeclarations

View File

@ -10,6 +10,7 @@ import * as File from 'vinyl';
import * as sm from 'source-map';
import * as path from 'path';
import * as sort from 'gulp-sort';
import { isESM } from './esm';
declare class FileSourceMap extends File {
public sourceMap: sm.RawSourceMap;
@ -231,14 +232,24 @@ module _nls {
.filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration)
.map(n => <ts.ImportEqualsDeclaration>n)
.filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference)
.filter(d => (<ts.ExternalModuleReference>d.moduleReference).expression.getText() === '\'vs/nls\'');
.filter(d => {
if (isESM()) {
return (<ts.ExternalModuleReference>d.moduleReference).expression.getText().endsWith(`/nls.js'`);
}
return (<ts.ExternalModuleReference>d.moduleReference).expression.getText() === '\'vs/nls\'';
});
// import ... from 'vs/nls';
const importDeclarations = imports
.filter(n => n.kind === ts.SyntaxKind.ImportDeclaration)
.map(n => <ts.ImportDeclaration>n)
.filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral)
.filter(d => d.moduleSpecifier.getText() === '\'vs/nls\'')
.filter(d => {
if (isESM()) {
return d.moduleSpecifier.getText().endsWith(`/nls.js'`);
}
return d.moduleSpecifier.getText() === '\'vs/nls\'';
})
.filter(d => !!d.importClause && !!d.importClause.namedBindings);
// `nls.localize(...)` calls

View File

@ -22,6 +22,7 @@ const i18n_1 = require("./i18n");
const stats_1 = require("./stats");
const util = require("./util");
const postcss_1 = require("./postcss");
const esm_1 = require("./esm");
const REPO_ROOT_PATH = path.join(__dirname, '../..');
function log(prefix, message) {
fancyLog(ansiColors.cyan('[' + prefix + ']'), message);
@ -148,7 +149,7 @@ const DEFAULT_FILE_HEADER = [
].join('\n');
function optimizeAMDTask(opts) {
const src = opts.src;
const entryPoints = opts.entryPoints;
const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm');
const resources = opts.resources;
const loaderConfig = opts.loaderConfig;
const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
@ -194,6 +195,162 @@ function optimizeAMDTask(opts) {
languages: opts.languages
}) : es.through());
}
function optimizeESMTask(opts, cjsOpts) {
// TODO@esm honor IEntryPoint#prepred/append (unused?)
const esbuild = require('esbuild');
const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
const sourcemaps = require('gulp-sourcemaps');
const resourcesStream = es.through(); // this stream will contain the resources
const bundlesStream = es.through(); // this stream will contain the bundled files
const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd');
if (cjsOpts) {
cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name }));
}
// TODO@esm remove hardcoded entry point and support `dest` of `IEntryPoint` or clean that up
entryPoints.push({ name: 'vs/base/worker/workerMain' });
const allMentionedModules = new Set();
for (const entryPoint of entryPoints) {
allMentionedModules.add(entryPoint.name);
entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules);
entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules);
}
// TODO@esm remove this from the bundle files
allMentionedModules.delete('vs/css');
const bundleAsync = async () => {
let bundleData;
const files = [];
const tasks = [];
for (const entryPoint of entryPoints) {
const t1 = performance.now();
console.log(`[bundle] STARTING '${entryPoint.name}'...`);
// support for 'dest' via esbuild#in/out
const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name;
// const dest = entryPoint.name;
// boilerplate massage
const banner = { js: '' };
const fs = await Promise.resolve().then(() => require('node:fs'));
const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');
banner.js += await fs.promises.readFile(tslibPath, 'utf-8');
const boilerplateTrimmer = {
name: 'boilerplate-trimmer',
setup(build) {
build.onLoad({ filter: /\.js$/ }, async (args) => {
const contents = await fs.promises.readFile(args.path, 'utf-8');
const newContents = bundle.removeAllTSBoilerplate(contents);
return { contents: newContents };
});
}
};
// support for 'preprend' via the esbuild#banner
if (entryPoint.prepend?.length) {
for (const item of entryPoint.prepend) {
const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path);
const source = await fs.promises.readFile(fullpath, 'utf8');
banner.js += source + '\n';
}
}
const task = esbuild.build({
logLevel: 'silent',
bundle: true,
external: entryPoint.exclude,
packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages
platform: 'neutral', // makes esm
format: 'esm',
plugins: [boilerplateTrimmer],
target: ['es2023'],
loader: {
'.ttf': 'file',
'.svg': 'file',
'.png': 'file',
'.sh': 'file',
},
banner,
entryPoints: [
{
in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`),
out: dest,
}
],
outdir: path.join(REPO_ROOT_PATH, opts.src),
write: false, // enables res.outputFiles
metafile: true, // enables res.metafile
}).then(res => {
console.log(`[bundle] DONE for '${entryPoint.name}' (${Math.round(performance.now() - t1)}ms)`);
if (opts.bundleInfo) {
// TODO@esm validate that bundleData is correct
bundleData ??= { graph: {}, bundles: {} };
function pathToModule(path) {
return path
.replace(new RegExp(`^${opts.src}\\/`), '')
.replace(/\.js$/, '');
}
for (const [path, value] of Object.entries(res.metafile.outputs)) {
const entryModule = pathToModule(path);
const inputModules = Object.keys(value.inputs).map(pathToModule);
bundleData.bundles[entryModule] = inputModules;
}
for (const [input, value] of Object.entries(res.metafile.inputs)) {
const dependencies = value.imports.map(i => pathToModule(i.path));
bundleData.graph[pathToModule(input)] = dependencies;
}
}
for (const file of res.outputFiles) {
let contents = file.contents;
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);
}
}
files.push(new VinylFile({
contents: Buffer.from(contents),
path: file.path,
base: path.join(REPO_ROOT_PATH, opts.src)
}));
}
});
// await task; // FORCE serial bundling (makes debugging easier)
tasks.push(task);
}
await Promise.all(tasks);
return { files, bundleData };
};
bundleAsync().then((output) => {
// bundle output (JS, CSS, SVG...)
es.readArray(output.files).pipe(bundlesStream);
// bundeInfo.json
const bundleInfoArray = [];
if (typeof output.bundleData === 'object') {
bundleInfoArray.push(new VinylFile({
path: 'bundleInfo.json',
base: '.',
contents: Buffer.from(JSON.stringify(output.bundleData, null, '\t'))
}));
}
es.readArray(bundleInfoArray).pipe(bundleInfoStream);
// forward all resources
gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream);
});
const result = es.merge(bundlesStream, resourcesStream, bundleInfoStream);
return result
.pipe(sourcemaps.write('./', {
sourceRoot: undefined,
addComment: true,
includeContent: true
}))
.pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({
out: opts.src,
fileHeader: bundledFileHeader,
languages: opts.languages
}) : es.through());
}
function optimizeCommonJSTask(opts) {
const esbuild = require('esbuild');
const src = opts.src;
@ -226,9 +383,15 @@ function optimizeLoaderTask(src, out, bundleLoader, bundledFileHeader = '', exte
}
function optimizeTask(opts) {
return function () {
const optimizers = [optimizeAMDTask(opts.amd)];
if (opts.commonJS) {
optimizers.push(optimizeCommonJSTask(opts.commonJS));
const optimizers = [];
if ((0, esm_1.isESM)('Running optimizer in ESM mode')) {
optimizers.push(optimizeESMTask(opts.amd, opts.commonJS));
}
else {
optimizers.push(optimizeAMDTask(opts.amd));
if (opts.commonJS) {
optimizers.push(optimizeCommonJSTask(opts.commonJS));
}
}
if (opts.manual) {
optimizers.push(optimizeManualTask(opts.manual));

View File

@ -17,6 +17,8 @@ import { Language, processNlsFiles } from './i18n';
import { createStatsStream } from './stats';
import * as util from './util';
import { gulpPostcss } from './postcss';
import type { Plugin } from 'esbuild';
import { isESM } from './esm';
const REPO_ROOT_PATH = path.join(__dirname, '../..');
@ -213,7 +215,7 @@ const DEFAULT_FILE_HEADER = [
function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream {
const src = opts.src;
const entryPoints = opts.entryPoints;
const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm');
const resources = opts.resources;
const loaderConfig = opts.loaderConfig;
const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
@ -271,6 +273,202 @@ function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream {
}) : es.through());
}
function optimizeESMTask(opts: IOptimizeAMDTaskOpts, cjsOpts?: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream {
// TODO@esm honor IEntryPoint#prepred/append (unused?)
const esbuild = require('esbuild') as typeof import('esbuild');
const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;
const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps');
const resourcesStream = es.through(); // this stream will contain the resources
const bundlesStream = es.through(); // this stream will contain the bundled files
const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json
const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd');
if (cjsOpts) {
cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name }));
}
// TODO@esm remove hardcoded entry point and support `dest` of `IEntryPoint` or clean that up
entryPoints.push({ name: 'vs/base/worker/workerMain' });
const allMentionedModules = new Set<string>();
for (const entryPoint of entryPoints) {
allMentionedModules.add(entryPoint.name);
entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules);
entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules);
}
// TODO@esm remove this from the bundle files
allMentionedModules.delete('vs/css');
const bundleAsync = async () => {
let bundleData: bundle.IBundleData | undefined;
const files: VinylFile[] = [];
const tasks: Promise<any>[] = [];
for (const entryPoint of entryPoints) {
const t1 = performance.now();
console.log(`[bundle] STARTING '${entryPoint.name}'...`);
// support for 'dest' via esbuild#in/out
const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name;
// const dest = entryPoint.name;
// boilerplate massage
const banner = { js: '' };
const fs = await import('node:fs');
const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');
banner.js += await fs.promises.readFile(tslibPath, 'utf-8');
const boilerplateTrimmer: Plugin = {
name: 'boilerplate-trimmer',
setup(build) {
build.onLoad({ filter: /\.js$/ }, async args => {
const contents = await fs.promises.readFile(args.path, 'utf-8');
const newContents = bundle.removeAllTSBoilerplate(contents);
return { contents: newContents };
});
}
};
// support for 'preprend' via the esbuild#banner
if (entryPoint.prepend?.length) {
for (const item of entryPoint.prepend) {
const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path);
const source = await fs.promises.readFile(fullpath, 'utf8');
banner.js += source + '\n';
}
}
const task = esbuild.build({
logLevel: 'silent',
bundle: true,
external: entryPoint.exclude,
packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages
platform: 'neutral', // makes esm
format: 'esm',
plugins: [boilerplateTrimmer],
target: ['es2023'],
loader: {
'.ttf': 'file',
'.svg': 'file',
'.png': 'file',
'.sh': 'file',
},
banner,
entryPoints: [
{
in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`),
out: dest,
}
],
outdir: path.join(REPO_ROOT_PATH, opts.src),
write: false, // enables res.outputFiles
metafile: true, // enables res.metafile
}).then(res => {
console.log(`[bundle] DONE for '${entryPoint.name}' (${Math.round(performance.now() - t1)}ms)`);
if (opts.bundleInfo) {
// TODO@esm validate that bundleData is correct
bundleData ??= { graph: {}, bundles: {} };
function pathToModule(path: string) {
return path
.replace(new RegExp(`^${opts.src}\\/`), '')
.replace(/\.js$/, '');
}
for (const [path, value] of Object.entries(res.metafile.outputs)) {
const entryModule = pathToModule(path);
const inputModules = Object.keys(value.inputs).map(pathToModule);
bundleData.bundles[entryModule] = inputModules;
}
for (const [input, value] of Object.entries(res.metafile.inputs)) {
const dependencies = value.imports.map(i => pathToModule(i.path));
bundleData.graph[pathToModule(input)] = dependencies;
}
}
for (const file of res.outputFiles) {
let contents = file.contents;
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);
}
}
files.push(new VinylFile({
contents: Buffer.from(contents),
path: file.path,
base: path.join(REPO_ROOT_PATH, opts.src)
}));
}
});
// await task; // FORCE serial bundling (makes debugging easier)
tasks.push(task);
}
await Promise.all(tasks);
return { files, bundleData };
};
bundleAsync().then((output) => {
// bundle output (JS, CSS, SVG...)
es.readArray(output.files).pipe(bundlesStream);
// bundeInfo.json
const bundleInfoArray: VinylFile[] = [];
if (typeof output.bundleData === 'object') {
bundleInfoArray.push(new VinylFile({
path: 'bundleInfo.json',
base: '.',
contents: Buffer.from(JSON.stringify(output.bundleData, null, '\t'))
}));
}
es.readArray(bundleInfoArray).pipe(bundleInfoStream);
// forward all resources
gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream);
});
const result = es.merge(
bundlesStream,
resourcesStream,
bundleInfoStream
);
return result
.pipe(sourcemaps.write('./', {
sourceRoot: undefined,
addComment: true,
includeContent: true
}))
.pipe(opts.languages && opts.languages.length ? processNlsFiles({
out: opts.src,
fileHeader: bundledFileHeader,
languages: opts.languages
}) : es.through());
}
export interface IOptimizeCommonJSTaskOpts {
/**
* The paths to consider for optimizing.
@ -360,9 +558,15 @@ export interface IOptimizeTaskOpts {
export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {
return function () {
const optimizers = [optimizeAMDTask(opts.amd)];
if (opts.commonJS) {
optimizers.push(optimizeCommonJSTask(opts.commonJS));
const optimizers: NodeJS.ReadWriteStream[] = [];
if (isESM('Running optimizer in ESM mode')) {
optimizers.push(optimizeESMTask(opts.amd, opts.commonJS));
} else {
optimizers.push(optimizeAMDTask(opts.amd));
if (opts.commonJS) {
optimizers.push(optimizeCommonJSTask(opts.commonJS));
}
}
if (opts.manual) {

View File

@ -15,6 +15,7 @@ const dep_lists_2 = require("./rpm/dep-lists");
const types_1 = require("./debian/types");
const types_2 = require("./rpm/types");
const product = require("../../product.json");
const esm_1 = require("../lib/esm");
// A flag that can easily be toggled.
// Make sure to compile the build directory after toggling the value.
// If false, we warn about new dependencies if they show up
@ -43,7 +44,8 @@ async function getDependencies(packageType, buildDir, applicationName, arch) {
throw new Error('Invalid RPM arch string ' + arch);
}
// Get the files for which we want to find dependencies.
const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked');
const canAsar = !(0, esm_1.isESM)('ASAR disabled in Linux builds'); // TODO@esm ASAR disabled in ESM
const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules');
const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']);
if (findResult.status) {
console.error('Error finding files:');

View File

@ -15,6 +15,7 @@ import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-list
import { DebianArchString, isDebianArchString } from './debian/types';
import { isRpmArchString, RpmArchString } from './rpm/types';
import product = require('../../product.json');
import { isESM } from '../lib/esm';
// A flag that can easily be toggled.
// Make sure to compile the build directory after toggling the value.
@ -47,7 +48,8 @@ export async function getDependencies(packageType: 'deb' | 'rpm', buildDir: stri
}
// Get the files for which we want to find dependencies.
const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked');
const canAsar = !isESM('ASAR disabled in Linux builds'); // TODO@esm ASAR disabled in ESM
const nativeModulesPath = path.join(buildDir, 'resources', 'app', canAsar ? 'node_modules.asar.unpacked' : 'node_modules');
const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']);
if (findResult.status) {
console.error('Error finding files:');

362
migrate.mjs Normal file
View File

@ -0,0 +1,362 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
// *****************************************************************
// * *
// * AMD-TO-ESM MIGRATION SCRIPT *
// * *
// *****************************************************************
import { readFileSync, writeFileSync } from 'node:fs';
import { join, extname, dirname, relative } from 'node:path';
import { preProcessFile } from 'typescript';
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
import { fileURLToPath } from 'node:url';
// @ts-expect-error
import watch from './build/lib/watch/index.js';
const enableWatching = !process.argv.includes('--disable-watch');
const srcFolder = fileURLToPath(new URL('src', import.meta.url));
const dstFolder = fileURLToPath(new URL('src2', import.meta.url));
const binaryFileExtensions = new Set([
'.svg', '.ttf', '.png', '.sh', '.html', '.json', '.zsh', '.scpt', '.mp3', '.fish', '.ps1', '.psm1', '.md', '.txt', '.zip', '.pdf', '.qwoff', '.jxs', '.tst', '.wuff', '.less', '.utf16le', '.snap', '.tsx'
]);
function migrate() {
console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
console.log(`STARTING MIGRATION of src to src2.`);
// installing watcher quickly to avoid missing early events
const watchSrc = enableWatching ? watch('src/**', { base: 'src', readDelay: 200 }) : undefined;
/** @type {string[]} */
const files = [];
readdir(srcFolder, files);
for (const filePath of files) {
const fileContents = readFileSync(filePath);
migrateOne(filePath, fileContents);
}
writeFileSync(join(dstFolder, '.gitignore'), `*`);
console.log(`~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`);
console.log(`COMPLETED MIGRATION of src to src2. You can now launch yarn watch or yarn watch-client`);
if (watchSrc) {
console.log(`WATCHING src for changes...`);
watchSrc.on('data', (e) => {
migrateOne(e.path, e.contents);
console.log(`Handled change event for ${e.path}.`);
});
}
}
function posixFilePath(filePath) {
return filePath.replace(/\\/g, '/');
}
/**
* @param filePath
* @param fileContents
*/
function migrateOne(filePath, fileContents) {
const fileExtension = extname(filePath);
if (fileExtension === '.ts') {
migrateTS(filePath, fileContents.toString());
} else if (fileExtension === '.js' || fileExtension === '.cjs') {
if (
posixFilePath(filePath).endsWith('vs/loader.js')
) {
// fake loader
writeDestFile(filePath, `(function () {
if (typeof require !== 'undefined') {
return;
}
globalThis.require = function () {
console.trace('[require(...)] this is ESM, no more AMD/CJS require');
};
globalThis.require.config = function () {
console.trace('[require.config(...)] this is ESM, no more AMD/CJS require');
};
})();`);
} else {
writeDestFile(filePath, fileContents);
}
} else if (fileExtension === '.mjs') {
writeDestFile(filePath, fileContents);
} else if (fileExtension === '.css') {
writeDestFile(filePath, fileContents);
} else if (filePath.endsWith('tsconfig.base.json')) {
const opts = JSON.parse(fileContents.toString());
opts.compilerOptions.module = 'ESNext';
opts.compilerOptions.allowSyntheticDefaultImports = true;
writeDestFile(filePath, JSON.stringify(opts, null, '\t'));
} else if (binaryFileExtensions.has(fileExtension)) {
writeDestFile(filePath, fileContents);
} else {
console.log(`ignoring ${filePath}`);
}
}
/**
* @param fileContents
* @typedef {{pos:number;end:number;}} Import
* @return
*/
function discoverImports(fileContents) {
const info = preProcessFile(fileContents);
const search = /export .* from ['"]([^'"]+)['"]/g;
/** typedef {Import[]} */
let result = [];
do {
const m = search.exec(fileContents);
if (!m) {
break;
}
const end = m.index + m[0].length - 2;
const pos = end - m[1].length;
result.push({ pos, end });
} while (true);
result = result.concat(info.importedFiles);
result.sort((a, b) => {
return a.pos - b.pos;
});
for (let i = 1; i < result.length; i++) {
const prev = result[i - 1];
const curr = result[i];
if (prev.pos === curr.pos) {
result.splice(i, 1);
i--;
}
}
return result;
}
/**
* @param filePath
* @param fileContents
*/
function migrateTS(filePath, fileContents) {
const filePathPosix = posixFilePath(filePath);
if (filePath.endsWith('.d.ts')) {
return writeDestFile(filePath, fileContents);
}
const imports = discoverImports(fileContents);
/** @type {Replacement[]} */
const replacements = [];
for (let i = imports.length - 1; i >= 0; i--) {
const pos = imports[i].pos + 1;
const end = imports[i].end + 1;
const importedFilename = fileContents.substring(pos, end);
/** @type {string} */
let importedFilepath;
if (/^vs\/css!/.test(importedFilename)) {
importedFilepath = importedFilename.substr('vs/css!'.length) + '.css';
} else {
importedFilepath = importedFilename;
}
/** @type {boolean} */
let isRelativeImport;
if (/(^\.\/)|(^\.\.\/)/.test(importedFilepath)) {
importedFilepath = join(dirname(filePath), importedFilepath);
isRelativeImport = true;
} else if (/^vs\//.test(importedFilepath)) {
importedFilepath = join(srcFolder, importedFilepath);
isRelativeImport = true;
} else {
importedFilepath = importedFilepath;
isRelativeImport = false;
}
/** @type {string} */
let replacementImport;
if (isRelativeImport) {
replacementImport = generateRelativeImport(filePath, importedFilepath);
} else {
replacementImport = importedFilepath;
}
replacements.push({ pos, end, text: replacementImport });
}
// replacements = replacements.concat(rewriteDefaultImports(fileContents));
fileContents = applyReplacements(fileContents, replacements);
fileContents = fileContents.replace(/require\.__\$__nodeRequire/g, 'require');
writeDestFile(filePath, fileContents);
}
/**
* @param filePath
* @param importedFilepath
*/
function generateRelativeImport(filePath, importedFilepath) {
/** @type {string} */
let relativePath;
// See https://github.com/microsoft/TypeScript/issues/16577#issuecomment-754941937
if (!importedFilepath.endsWith('.css') && !importedFilepath.endsWith('.cjs')) {
importedFilepath = `${importedFilepath}.js`;
}
relativePath = relative(dirname(filePath), `${importedFilepath}`);
relativePath = relativePath.replace(/\\/g, '/');
if (!/(^\.\/)|(^\.\.\/)/.test(relativePath)) {
relativePath = './' + relativePath;
}
return relativePath;
}
/** @typedef {{pos:number;end:number;text:string;}} Replacement */
/**
* @param str
* @param replacements
*/
function applyReplacements(str, replacements) {
replacements.sort((a, b) => {
return a.pos - b.pos;
});
/** @type {string[]} */
const result = [];
let lastEnd = 0;
for (const replacement of replacements) {
const { pos, end, text } = replacement;
result.push(str.substring(lastEnd, pos));
result.push(text);
lastEnd = end;
}
result.push(str.substring(lastEnd, str.length));
return result.join('');
}
/**
* @param srcFilePath
* @param fileContents
*/
function writeDestFile(srcFilePath, fileContents) {
const destFilePath = srcFilePath.replace(srcFolder, dstFolder);
ensureDir(dirname(destFilePath));
if (/(\.ts$)|(\.js$)|(\.html$)/.test(destFilePath)) {
fileContents = toggleComments(fileContents);
}
/** @type {Buffer | undefined} */
let existingFileContents = undefined;
try {
existingFileContents = readFileSync(destFilePath);
} catch (err) { }
if (!buffersAreEqual(existingFileContents, fileContents)) {
writeFileSync(destFilePath, fileContents);
}
/**
* @param fileContents
*/
function toggleComments(fileContents) {
const lines = String(fileContents).split(/\r\n|\r|\n/);
let mode = 0;
let didChange = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (mode === 0) {
if (/\/\/ ESM-comment-begin/.test(line)) {
mode = 1;
continue;
}
if (/\/\/ ESM-uncomment-begin/.test(line)) {
mode = 2;
continue;
}
continue;
}
if (mode === 1) {
if (/\/\/ ESM-comment-end/.test(line)) {
mode = 0;
continue;
}
didChange = true;
lines[i] = '// ' + line;
continue;
}
if (mode === 2) {
if (/\/\/ ESM-uncomment-end/.test(line)) {
mode = 0;
continue;
}
didChange = true;
lines[i] = line.replace(/^(\s*)\/\/ ?/, function (_, indent) {
return indent;
});
}
}
if (didChange) {
return lines.join('\n');
}
return fileContents;
}
}
/**
* @param existingFileContents
* @param fileContents
*/
function buffersAreEqual(existingFileContents, fileContents) {
if (!existingFileContents) {
return false;
}
if (typeof fileContents === 'string') {
fileContents = Buffer.from(fileContents);
}
return existingFileContents.equals(fileContents);
}
const ensureDirCache = new Set();
function ensureDir(dirPath) {
if (ensureDirCache.has(dirPath)) {
return;
}
ensureDirCache.add(dirPath);
ensureDir(dirname(dirPath));
if (!existsSync(dirPath)) {
mkdirSync(dirPath);
}
}
function readdir(dirPath, result) {
const entries = readdirSync(dirPath);
for (const entry of entries) {
const entryPath = join(dirPath, entry);
const stat = statSync(entryPath);
if (stat.isDirectory()) {
readdir(join(dirPath, entry), result);
} else {
result.push(entryPath);
}
}
}
migrate();