Path: blob/main/extensions/copilot/test/simulation/fixtures/edit/issue-8129/optimize.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as ansiColors from 'ansi-colors';6import * as esbuild from 'esbuild';7import * as es from 'event-stream';8import * as fancyLog from 'fancy-log';9import * as fs from 'fs';10import * as gulp from 'gulp';11import * as concat from 'gulp-concat';12import * as filter from 'gulp-filter';13import * as sourcemaps from 'gulp-sourcemaps';14import * as path from 'path';15import * as pump from 'pump';16import * as VinylFile from 'vinyl';17import { isAMD } from './amd';18import * as bundle from './bundle';19import { Language, processNlsFiles } from './i18n';20import { gulpPostcss } from './postcss';21import { createStatsStream } from './stats';22import * as util from './util';2324const REPO_ROOT_PATH = path.join(__dirname, '../..');2526function log(prefix: string, message: string): void {27fancyLog(ansiColors.cyan('[' + prefix + ']'), message);28}2930export function loaderConfig() {31const result: any = {32paths: {33'vs': 'out-build/vs',34'vscode': 'empty:'35},36amdModulesPattern: /^vs\//37};3839result['vs/css'] = { inlineResources: true };4041return result;42}4344const IS_OUR_COPYRIGHT_REGEXP = /Copyright \(C\) Microsoft Corporation/i;4546function loaderPlugin(src: string, base: string, amdModuleId: string | undefined): NodeJS.ReadWriteStream {47return (48gulp49.src(src, { base })50.pipe(es.through(function (data: VinylFile) {51if (amdModuleId) {52let contents = data.contents.toString('utf8');53contents = contents.replace(/^define\(/m, `define("${amdModuleId}",`);54data.contents = Buffer.from(contents);55}56this.emit('data', data);57}))58);59}6061function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: util.IExternalLoaderInfo): NodeJS.ReadWriteStream {62let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` });63if (bundleLoader) {64loaderStream = es.merge(65loaderStream,66loaderPlugin(`${src}/vs/css.js`, `${src}`, 'vs/css')67);68}6970const files: VinylFile[] = [];71const order = (f: VinylFile) => {72if (f.path.endsWith('loader.js')) {73return 0;74}75if (f.path.endsWith('css.js')) {76return 1;77}78return 2;79};8081return (82loaderStream83.pipe(es.through(function (data) {84files.push(data);85}, function () {86files.sort((a, b) => {87return order(a) - order(b);88});89files.unshift(new VinylFile({90path: 'fake',91base: '.',92contents: Buffer.from(bundledFileHeader)93}));94if (externalLoaderInfo !== undefined) {95files.push(new VinylFile({96path: 'fake2',97base: '.',98contents: Buffer.from(emitExternalLoaderInfo(externalLoaderInfo))99}));100}101for (const file of files) {102this.emit('data', file);103}104this.emit('end');105}))106.pipe(concat('vs/loader.js'))107);108}109110function emitExternalLoaderInfo(externalLoaderInfo: util.IExternalLoaderInfo): string {111const externalBaseUrl = externalLoaderInfo.baseUrl;112externalLoaderInfo.baseUrl = '$BASE_URL';113114// If defined, use the runtime configured baseUrl.115const code = `116(function() {117const baseUrl = require.getConfig().baseUrl || ${JSON.stringify(externalBaseUrl)};118require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});119})();`;120return code.replace('"$BASE_URL"', 'baseUrl');121}122123function toConcatStream(src: string, bundledFileHeader: string, sources: bundle.IFile[], dest: string, fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {124const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest);125126// If a bundle ends up including in any of the sources our copyright, then127// insert a fake source at the beginning of each bundle with our copyright128let containsOurCopyright = false;129for (let i = 0, len = sources.length; i < len; i++) {130const fileContents = sources[i].contents;131if (IS_OUR_COPYRIGHT_REGEXP.test(fileContents)) {132containsOurCopyright = true;133break;134}135}136137if (containsOurCopyright) {138sources.unshift({139path: null,140contents: bundledFileHeader141});142}143144const treatedSources = sources.map(function (source) {145const root = source.path ? REPO_ROOT_PATH.replace(/\\/g, '/') : '';146const base = source.path ? root + `/${src}` : '.';147const path = source.path ? root + '/' + source.path.replace(/\\/g, '/') : 'fake';148const contents = source.path ? fileContentMapper(source.contents, path) : source.contents;149150return new VinylFile({151path: path,152base: base,153contents: Buffer.from(contents)154});155});156157return es.readArray(treatedSources)158.pipe(useSourcemaps ? util.loadSourcemaps() : es.through())159.pipe(concat(dest))160.pipe(createStatsStream(dest));161}162163function toBundleStream(src: string, bundledFileHeader: string, bundles: bundle.IConcatFile[], fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream {164return es.merge(bundles.map(function (bundle) {165return toConcatStream(src, bundledFileHeader, bundle.sources, bundle.dest, fileContentMapper);166}));167}168169export interface IOptimizeAMDTaskOpts {170/**171* The folder to read files from.172*/173src: string;174/**175* (for AMD files, will get bundled and get Copyright treatment)176*/177entryPoints: bundle.IEntryPoint[];178/**179* (svg, etc.)180*/181resources: string[];182loaderConfig: any;183/**184* Additional info we append to the end of the loader185*/186externalLoaderInfo?: util.IExternalLoaderInfo;187/**188* (true by default - append css and nls to loader)189*/190bundleLoader?: boolean;191/**192* (basically the Copyright treatment)193*/194header?: string;195/**196* (emit bundleInfo.json file)197*/198bundleInfo: boolean;199/**200* Language configuration.201*/202languages?: Language[];203/**204* File contents interceptor205* @param contents The contents of the file206* @param path The absolute file path, always using `/`, even on Windows207*/208fileContentMapper?: (contents: string, path: string) => string;209}210211const DEFAULT_FILE_HEADER = [212'/*!--------------------------------------------------------',213' * Copyright (C) Microsoft Corporation. All rights reserved.',214' *--------------------------------------------------------*/'215].join('\n');216217function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream {218const src = opts.src;219const entryPoints = opts.entryPoints.filter(d => d.target !== 'esm');220const resources = opts.resources;221const loaderConfig = opts.loaderConfig;222const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER;223const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents);224225const bundlesStream = es.through(); // this stream will contain the bundled files226const resourcesStream = es.through(); // this stream will contain the resources227const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json228229bundle.bundle(entryPoints, loaderConfig, function (err, result) {230if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); }231232toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream);233234// Remove css inlined resources235const filteredResources = resources.slice();236result.cssInlinedResources.forEach(function (resource) {237if (process.env['VSCODE_BUILD_VERBOSE']) {238log('optimizer', 'excluding inlined: ' + resource);239}240filteredResources.push('!' + resource);241});242gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream);243244const bundleInfoArray: VinylFile[] = [];245if (opts.bundleInfo) {246bundleInfoArray.push(new VinylFile({247path: 'bundleInfo.json',248base: '.',249contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t'))250}));251}252es.readArray(bundleInfoArray).pipe(bundleInfoStream);253});254255const result = es.merge(256loader(src, bundledFileHeader, false, opts.externalLoaderInfo),257bundlesStream,258resourcesStream,259bundleInfoStream260);261262return result263.pipe(sourcemaps.write('./', {264sourceRoot: undefined,265addComment: true,266includeContent: true267}))268.pipe(opts.languages && opts.languages.length ? processNlsFiles({269out: opts.src,270fileHeader: bundledFileHeader,271languages: opts.languages272}) : es.through());273}274275function optimizeESMTask(opts: IOptimizeAMDTaskOpts, cjsOpts?: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream {276const resourcesStream = es.through(); // this stream will contain the resources277const bundlesStream = es.through(); // this stream will contain the bundled files278279const entryPoints = opts.entryPoints.filter(d => d.target !== 'amd');280if (cjsOpts) {281cjsOpts.entryPoints.forEach(entryPoint => entryPoints.push({ name: path.parse(entryPoint).name }));282}283284const allMentionedModules = new Set<string>();285for (const entryPoint of entryPoints) {286allMentionedModules.add(entryPoint.name);287entryPoint.include?.forEach(allMentionedModules.add, allMentionedModules);288entryPoint.exclude?.forEach(allMentionedModules.add, allMentionedModules);289}290291allMentionedModules.delete('vs/css'); // TODO@esm remove this when vs/css is removed292293const bundleAsync = async () => {294295const files: VinylFile[] = [];296const tasks: Promise<any>[] = [];297298for (const entryPoint of entryPoints) {299300console.log(`[bundle] '${entryPoint.name}'`);301302// support for 'dest' via esbuild#in/out303const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name;304305// boilerplate massage306const banner = { js: '' };307const tslibPath = path.join(require.resolve('tslib'), '../tslib.es6.js');308banner.js += await fs.promises.readFile(tslibPath, 'utf-8');309310const boilerplateTrimmer: esbuild.Plugin = {311name: 'boilerplate-trimmer',312setup(build) {313build.onLoad({ filter: /\.js$/ }, async args => {314const contents = await fs.promises.readFile(args.path, 'utf-8');315const newContents = bundle.removeAllTSBoilerplate(contents);316return { contents: newContents };317});318}319};320321// support for 'preprend' via the esbuild#banner322if (entryPoint.prepend?.length) {323for (const item of entryPoint.prepend) {324const fullpath = path.join(REPO_ROOT_PATH, opts.src, item.path);325const source = await fs.promises.readFile(fullpath, 'utf8');326banner.js += source + '\n';327}328}329330const task = esbuild.build({331bundle: true,332external: entryPoint.exclude,333packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages334platform: 'neutral', // makes esm335format: 'esm',336sourcemap: 'external',337plugins: [boilerplateTrimmer],338target: ['es2022'],339loader: {340'.ttf': 'file',341'.svg': 'file',342'.png': 'file',343'.sh': 'file',344},345assetNames: 'media/[name]', // moves media assets into a sub-folder "media"346banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge347entryPoints: [348{349in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`),350out: dest,351}352],353outdir: path.join(REPO_ROOT_PATH, opts.src),354write: false, // enables res.outputFiles355metafile: true, // enables res.metafile356357}).then(res => {358for (const file of res.outputFiles) {359360let contents = file.contents;361let sourceMapFile: esbuild.OutputFile | undefined = undefined;362363if (file.path.endsWith('.js')) {364365if (opts.fileContentMapper) {366// UGLY the fileContentMapper is per file but at this point we have all files367// bundled already. So, we call the mapper for the same contents but each file368// that has been included in the bundle...369let newText = file.text;370for (const input of Object.keys(res.metafile.inputs)) {371newText = opts.fileContentMapper(newText, input);372}373contents = Buffer.from(newText);374}375376sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`);377}378379const fileProps = {380contents: Buffer.from(contents),381sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps382path: file.path,383base: path.join(REPO_ROOT_PATH, opts.src)384};385files.push(new VinylFile(fileProps));386}387});388389// await task; // FORCE serial bundling (makes debugging easier)390tasks.push(task);391}392393await Promise.all(tasks);394return { files };395};396397bundleAsync().then((output) => {398399// bundle output (JS, CSS, SVG...)400es.readArray(output.files).pipe(bundlesStream);401402// forward all resources403gulp.src(opts.resources, { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream);404});405406const result = es.merge(407bundlesStream,408resourcesStream409);410411return result412.pipe(sourcemaps.write('./', {413sourceRoot: undefined,414addComment: true,415includeContent: true416}))417.pipe(opts.languages && opts.languages.length ? processNlsFiles({418out: opts.src,419fileHeader: opts.header || DEFAULT_FILE_HEADER,420languages: opts.languages421}) : es.through());422}423424export interface IOptimizeCommonJSTaskOpts {425/**426* The paths to consider for optimizing.427*/428entryPoints: string[];429/**430* The folder to read files from.431*/432src: string;433/**434* ESBuild `platform` option: https://esbuild.github.io/api/#platform435*/436platform: 'browser' | 'node' | 'neutral';437/**438* ESBuild `external` option: https://esbuild.github.io/api/#external439*/440external: string[];441}442443function optimizeCommonJSTask(opts: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream {444const src = opts.src;445const entryPoints = opts.entryPoints;446447return gulp.src(entryPoints, { base: `${src}`, allowEmpty: true })448.pipe(es.map((f: any, cb) => {449esbuild.build({450entryPoints: [f.path],451bundle: true,452platform: opts.platform,453write: false,454external: opts.external455}).then(res => {456const jsFile = res.outputFiles[0];457f.contents = Buffer.from(jsFile.contents);458459cb(undefined, f);460});461}));462}463464export interface IOptimizeManualTaskOpts {465/**466* The paths to consider for concatenation. The entries467* will be concatenated in the order they are provided.468*/469src: string[];470/**471* Destination target to concatenate the entryPoints into.472*/473out: string;474}475476function optimizeManualTask(options: IOptimizeManualTaskOpts[]): NodeJS.ReadWriteStream {477const concatenations = options.map(opt => {478return gulp479.src(opt.src)480.pipe(concat(opt.out));481});482483return es.merge(...concatenations);484}485486export function optimizeLoaderTask(src: string, out: string, bundleLoader: boolean, bundledFileHeader = '', externalLoaderInfo?: util.IExternalLoaderInfo): () => NodeJS.ReadWriteStream {487return () => loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo).pipe(gulp.dest(out));488}489490export interface IOptimizeTaskOpts {491/**492* Destination folder for the optimized files.493*/494out: string;495/**496* Optimize AMD modules (using our AMD loader).497*/498amd: IOptimizeAMDTaskOpts;499/**500* Optimize CommonJS modules (using esbuild).501*/502commonJS?: IOptimizeCommonJSTaskOpts;503/**504* Optimize manually by concatenating files.505*/506manual?: IOptimizeManualTaskOpts[];507}508509export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream {510return function () {511const optimizers: NodeJS.ReadWriteStream[] = [];512if (!isAMD()) {513optimizers.push(optimizeESMTask(opts.amd, opts.commonJS));514} else {515optimizers.push(optimizeAMDTask(opts.amd));516517if (opts.commonJS) {518optimizers.push(optimizeCommonJSTask(opts.commonJS));519}520}521522if (opts.manual) {523optimizers.push(optimizeManualTask(opts.manual));524}525526return es.merge(...optimizers).pipe(gulp.dest(opts.out));527};528}529530export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void {531const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined;532533return cb => {534const cssnano = require('cssnano') as typeof import('cssnano');535const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin');536537const jsFilter = filter('**/*.js', { restore: true });538const cssFilter = filter('**/*.css', { restore: true });539const svgFilter = filter('**/*.svg', { restore: true });540541pump(542gulp.src([src + '/**', '!' + src + '/**/*.map']),543jsFilter,544sourcemaps.init({ loadMaps: true }),545es.map((f: any, cb) => {546esbuild.build({547entryPoints: [f.path],548minify: true,549sourcemap: 'external',550outdir: '.',551platform: 'node',552target: ['es2022'],553write: false554}).then(res => {555const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!;556const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!;557558const contents = Buffer.from(jsFile.contents);559const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g);560if (unicodeMatch) {561cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`));562} else {563f.contents = contents;564f.sourceMap = JSON.parse(sourceMapFile.text);565566cb(undefined, f);567}568}, cb);569}),570jsFilter.restore,571cssFilter,572gulpPostcss([cssnano({ preset: 'default' })]),573cssFilter.restore,574svgFilter,575svgmin(),576svgFilter.restore,577sourcemaps.write('./', {578sourceMappingURL,579sourceRoot: undefined,580includeContent: true,581addComment: true582} as any),583gulp.dest(src + '-min'),584(err: any) => cb(err));585};586}587588589