Skip to content

Commit

Permalink
chore(esbuild): build the internal module with esbuild (#5276)
Browse files Browse the repository at this point in the history
This adds the ability to build the modules within `internal/` with
Esbuild, replicating the existing Rollup build.

STENCIL-990
  • Loading branch information
alicewriteswrongs authored Feb 20, 2024
1 parent 5c54f38 commit b09f233
Show file tree
Hide file tree
Showing 17 changed files with 462 additions and 28 deletions.
2 changes: 1 addition & 1 deletion scripts/bundles/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function internal(opts: BuildOptions) {
return [...clientPlatformBundle, ...hydratePlatformBundles, ...testingPlatform, await internalAppData(opts)];
}

async function copyStencilInternalDts(opts: BuildOptions, outputInternalDir: string) {
export async function copyStencilInternalDts(opts: BuildOptions, outputInternalDir: string) {
const declarationsInputDir = join(opts.buildDir, 'declarations');

// copy to @stencil/core/internal
Expand Down
3 changes: 3 additions & 0 deletions scripts/bundles/plugins/alias-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export function aliasPlugin(opts: BuildOptions): Plugin {
if (id === '@utils') {
return join(opts.buildDir, 'utils', 'index.js');
}
if (id === '@utils/shadow-css') {
return join(opts.buildDir, 'utils', 'shadow-css.js');
}
if (id === '@environment') {
return join(opts.buildDir, 'compiler', 'sys', 'environment.js');
}
Expand Down
2 changes: 2 additions & 0 deletions scripts/esbuild/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getOptions } from '../utils/options';
import { buildCli } from './cli';
import { buildCompiler } from './compiler';
import { buildDevServer } from './dev-server';
import { buildInternal } from './internal';
import { buildMockDoc } from './mock-doc';
import { buildScreenshot } from './screenshot';
import { buildSysNode } from './sys-node';
Expand All @@ -23,6 +24,7 @@ async function main() {
buildScreenshot(opts),
buildSysNode(opts),
buildTesting(opts),
buildInternal(opts),
]);
}

Expand Down
56 changes: 56 additions & 0 deletions scripts/esbuild/internal-app-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { BuildOptions as ESBuildOptions } from 'esbuild';
import fs from 'fs-extra';
import { join } from 'path';

import { BuildOptions } from '../utils/options';
import { writePkgJson } from '../utils/write-pkg-json';
import { getBaseEsbuildOptions } from './util';

/**
* Get an object containing ESbuild options to build the internal app data
* file. This function also performs relevant side-effects, like writing a
* `package.json` file to disk.
*
* @param opts build options
* @returns a Promise wrapping an array of ESbuild option objects
*/
export async function getInternalAppDataBundles(opts: BuildOptions): Promise<ESBuildOptions[]> {
const appDataBuildDir = join(opts.buildDir, 'app-data');
const appDataSrcDir = join(opts.srcDir, 'app-data');
const outputInternalAppDataDir = join(opts.output.internalDir, 'app-data');

await fs.emptyDir(outputInternalAppDataDir);

// copy @stencil/core/internal/app-data/index.d.ts
await fs.copyFile(join(appDataBuildDir, 'index.d.ts'), join(outputInternalAppDataDir, 'index.d.ts'));

// write @stencil/core/internal/app-data/package.json
writePkgJson(opts, outputInternalAppDataDir, {
name: '@stencil/core/internal/app-data',
description: 'Used for default app data and build conditionals within builds.',
main: 'index.cjs',
module: 'index.js',
types: 'index.d.ts',
sideEffects: false,
});

const appDataBaseOptions: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [join(appDataSrcDir, 'index.ts')],
platform: 'node',
};

const appDataESM: ESBuildOptions = {
...appDataBaseOptions,
format: 'esm',
outfile: join(outputInternalAppDataDir, 'index.js'),
};

const appDataCJS: ESBuildOptions = {
...appDataBaseOptions,
format: 'cjs',
outfile: join(outputInternalAppDataDir, 'index.cjs'),
};

return [appDataESM, appDataCJS];
}
111 changes: 111 additions & 0 deletions scripts/esbuild/internal-platform-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { BuildOptions as ESBuildOptions } from 'esbuild';
import { replace } from 'esbuild-plugin-replace';
import fs from 'fs-extra';
import glob from 'glob';
import { join } from 'path';

import { getBanner } from '../utils/banner';
import { BuildOptions, createReplaceData } from '../utils/options';
import { writePkgJson } from '../utils/write-pkg-json';
import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util';

/**
* Create objects containing ESbuild options for the two bundles which need to
* be written to `internal/client`. This also performs relevant side-effects,
* like clearing out the directory and writing a `package.json` script to disk.
*
* @param opts build options
* @returns an array of ESBuild option objects
*/
export async function getInternalClientBundle(opts: BuildOptions): Promise<ESBuildOptions[]> {
const inputClientDir = join(opts.srcDir, 'client');
const outputInternalClientDir = join(opts.output.internalDir, 'client');
const outputInternalClientPolyfillsDir = join(outputInternalClientDir, 'polyfills');

await fs.emptyDir(outputInternalClientDir);
await fs.emptyDir(outputInternalClientPolyfillsDir);

await copyPolyfills(opts, outputInternalClientPolyfillsDir);

// write @stencil/core/internal/client/package.json
writePkgJson(opts, outputInternalClientDir, {
name: '@stencil/core/internal/client',
description:
'Stencil internal client platform to be imported by the Stencil Compiler and internal runtime. Breaking changes can and will happen at any time.',
main: 'index.js',
sideEffects: false,
});

const internalClientAliases = getEsbuildAliases();
internalClientAliases['@platform'] = join(inputClientDir, 'index.ts');

const clientExternal = getEsbuildExternalModules(opts, opts.output.internalDir);

const internalClientBundle: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [join(inputClientDir, 'index.ts')],
format: 'esm',
outfile: join(outputInternalClientDir, 'index.js'),
platform: 'node',
external: clientExternal,
alias: internalClientAliases,
banner: {
js: getBanner(opts, 'Stencil Client Platform'),
},
plugins: [
replace(createReplaceData(opts)),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
externalAlias('@utils/shadow-css', './shadow-css.js'),
// we want to get the esm, not the cjs, since we're creating an esm
// bundle here
externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'),
],
};

const patchBrowserAliases = getEsbuildAliases();

const polyfills = await fs.readdir(join(opts.srcDir, 'client', 'polyfills'));
for (const polyFillFile of polyfills) {
patchBrowserAliases[join('./polyfills', polyFillFile)] = join(opts.srcDir, 'client', 'polyfills');
}

const patchBrowserExternal = [
...getEsbuildExternalModules(opts, opts.output.internalDir),
'@stencil/core',
'@stencil/core/mock-doc',
];

const internalClientPatchBrowserBundle: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [join(inputClientDir, 'client-patch-browser.ts')],
format: 'esm',
outfile: join(outputInternalClientDir, 'patch-browser.js'),
platform: 'node',
external: patchBrowserExternal,
alias: patchBrowserAliases,
banner: {
js: getBanner(opts, 'Stencil Client Patch Browser'),
},
plugins: [
replace(createReplaceData(opts)),
externalAlias('@platform', '@stencil/core'),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
],
};

return [internalClientBundle, internalClientPatchBrowserBundle];
}

async function copyPolyfills(opts: BuildOptions, outputInternalClientPolyfillsDir: string) {
const srcPolyfillsDir = join(opts.srcDir, 'client', 'polyfills');

const srcPolyfillFiles = glob.sync('*.js', { cwd: srcPolyfillsDir });

await Promise.all(
srcPolyfillFiles.map(async (fileName) => {
const src = join(srcPolyfillsDir, fileName);
const dest = join(outputInternalClientPolyfillsDir, fileName);
await fs.copyFile(src, dest);
}),
);
}
89 changes: 89 additions & 0 deletions scripts/esbuild/internal-platform-hydrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { BuildOptions as ESBuildOptions } from 'esbuild';
import fs from 'fs-extra';
import { join } from 'path';

import { getBanner } from '../utils/banner';
import { bundleDts } from '../utils/bundle-dts';
import { BuildOptions } from '../utils/options';
import { writePkgJson } from '../utils/write-pkg-json';
import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util';

/**
* Create objects containing ESbuild options for the two bundles comprising
* the hydrate platform. This also performs relevant side-effects, like
* clearing out a directory and writing a `package.json` script to disk.
*
* @param opts build options
* @returns an array of ESBuild option objects
*/
export async function getInternalPlatformHydrateBundles(opts: BuildOptions): Promise<ESBuildOptions[]> {
const inputHydrateDir = join(opts.buildDir, 'hydrate');
const hydrateSrcDir = join(opts.srcDir, 'hydrate');
const outputInternalHydrateDir = join(opts.output.internalDir, 'hydrate');

await fs.emptyDir(outputInternalHydrateDir);

// write @stencil/core/internal/hydrate/package.json
writePkgJson(opts, outputInternalHydrateDir, {
name: '@stencil/core/internal/hydrate',
description:
'Stencil internal hydrate platform to be imported by the Stencil Compiler. Breaking changes can and will happen at any time.',
main: 'index.js',
});

await createHydrateRunnerDtsBundle(opts, inputHydrateDir, outputInternalHydrateDir);

const hydratePlatformInput = join(hydrateSrcDir, 'platform', 'index.js');

const external = [...getEsbuildExternalModules(opts, outputInternalHydrateDir), '@stencil/core/mock-doc'];

const internalHydrateAliases = getEsbuildAliases();
internalHydrateAliases['@platform'] = hydratePlatformInput;

const internalHydratePlatformBundle: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [hydratePlatformInput],
format: 'esm',
platform: 'node',
outfile: join(outputInternalHydrateDir, 'index.js'),
external,
alias: internalHydrateAliases,
banner: {
js: getBanner(opts, 'Stencil Hydrate Platform'),
},
plugins: [
externalAlias('@utils/shadow-css', '../client/shadow-css.js'),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
// this needs to be externalized and also pointed at the esm version
externalAlias('@stencil/core/mock-doc', '../../mock-doc/index.js'),
],
};

const internalHydrateRunnerBundle: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [join(hydrateSrcDir, 'runner', 'index.js')],
external,
format: 'esm',
platform: 'node',
outfile: join(outputInternalHydrateDir, 'runner.js'),
banner: {
js: getBanner(opts, 'Stencil Hydrate Runner'),
},
plugins: [
externalAlias('@utils/shadow-css', '../client/shadow-css.js'),
externalAlias('@app-data', '@stencil/core/internal/app-data'),
externalAlias('@hydrate-factory', '@stencil/core/hydrate-factory'),
],
};

return [internalHydratePlatformBundle, internalHydrateRunnerBundle];
}

async function createHydrateRunnerDtsBundle(opts: BuildOptions, inputHydrateDir: string, outputDir: string) {
// bundle @stencil/core/internal/hydrate/runner.d.ts
const dtsEntry = join(inputHydrateDir, 'runner', 'index.d.ts');
const dtsContent = await bundleDts(opts, dtsEntry);

const outputPath = join(outputDir, 'runner.d.ts');
await fs.writeFile(outputPath, dtsContent);
}
54 changes: 54 additions & 0 deletions scripts/esbuild/internal-platform-testing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { BuildOptions as ESBuildOptions } from 'esbuild';
import fs from 'fs-extra';
import { join } from 'path';

import { BuildOptions } from '../utils/options';
import { writePkgJson } from '../utils/write-pkg-json';
import { externalAlias, getBaseEsbuildOptions, getEsbuildAliases, getEsbuildExternalModules } from './util';

/**
* Get an ESBuild configuration object for the internal testing bundle. This
* function also has side-effects which set things up for the bundle to be built
* correctly, like writing a `package.json` file to disk.
*
* @param opts build options
* @returns a promise wrapping an object holding options for ESBuild
*/
export async function getInternalTestingBundle(opts: BuildOptions): Promise<ESBuildOptions> {
const inputTestingPlatform = join(opts.srcDir, 'testing', 'platform', 'index.ts');
const outputTestingPlatformDir = join(opts.output.internalDir, 'testing');

await fs.emptyDir(outputTestingPlatformDir);

// write @stencil/core/internal/testing/package.json
writePkgJson(opts, outputTestingPlatformDir, {
name: '@stencil/core/internal/testing',
description:
'Stencil internal testing platform to be imported by the Stencil Compiler. Breaking changes can and will happen at any time.',
main: 'index.js',
});

const internalTestingAliases = {
...getEsbuildAliases(),
'@platform': inputTestingPlatform,
};

const external = [...getEsbuildExternalModules(opts, opts.output.internalDir), '@stencil/core/mock-doc'];

const internalTestingBuildOptions: ESBuildOptions = {
...getBaseEsbuildOptions(),
entryPoints: [inputTestingPlatform],
bundle: true,
format: 'cjs',
outfile: join(outputTestingPlatformDir, 'index.js'),
platform: 'node',
logLevel: 'info',
external,
alias: internalTestingAliases,
plugins: [
externalAlias('@app-data', '@stencil/core/internal/app-data'),
externalAlias('@utils/shadow-css', '../client/shadow-css.js'),
],
};
return internalTestingBuildOptions;
}
Loading

0 comments on commit b09f233

Please sign in to comment.