Skip to content

Commit

Permalink
fix(build): do not copy polyfills to the dist OT unless building es5 (
Browse files Browse the repository at this point in the history
#5725)

Prior to this change Stencil will copy polyfills to the `dist` output
target whether or not the user has indicated they'll be necessary.

The polyfills comprise two things: copying the polyfills themselves into
the 'loader' path, and adding code to the lazy-loader entry points which
loads those polyfills. Instead of just assuming that the user wants
this, we now gate this behavior on whether `buildEs5` is set on the
Stencil configuration.

fixes #5416
STENCIL-1288
  • Loading branch information
alicewriteswrongs committed May 7, 2024
1 parent 43454bb commit 945df46
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 16 deletions.
13 changes: 12 additions & 1 deletion src/compiler/output-targets/dist-lazy/generate-esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,25 @@ export const generateEsm = async (
'',
);

await copyPolyfills(config, compilerCtx, esmOutputs);
if (config.buildEs5) {
await copyPolyfills(config, compilerCtx, esmOutputs);
}
await generateShortcuts(config, compilerCtx, outputTargets, output);
}
}

return { name: 'esm', buildCtx };
};

/**
* Copy polyfills from `$INSTALL_DIR/internal/client/polyfills` to the lazy
* loader output directory where $INSTALL_DIR is the directory in which the
* `@stencil/core` package is installed.
*
* @param config a validated Stencil configuration
* @param compilerCtx the current compiler context
* @param outputTargets dist-lazy output targets
*/
const copyPolyfills = async (
config: d.ValidatedConfig,
compilerCtx: d.CompilerCtx,
Expand Down
52 changes: 37 additions & 15 deletions src/compiler/output-targets/output-lazy-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,33 @@ const generateLoader = async (
2,
);

const polyfillsEntryPoint = join(es2017Dir, 'polyfills/index.js');
const polyfillsExport = `export * from '${relative(loaderPath, polyfillsEntryPoint)}';`;

const es5EntryPoint = join(es5Dir, 'loader.js');
const indexContent = filterAndJoin([
generatePreamble(config),
es5HtmlElement,
config.buildEs5 ? polyfillsExport : null,
`export * from '${relative(loaderPath, es5EntryPoint)}';`,
]);

const es2017EntryPoint = join(es2017Dir, 'loader.js');
const polyfillsEntryPoint = join(es2017Dir, 'polyfills/index.js');
const indexES2017Content = filterAndJoin([
generatePreamble(config),
config.buildEs5 ? polyfillsExport : null,
`export * from '${relative(loaderPath, es2017EntryPoint)}';`,
]);

const cjsEntryPoint = join(cjsDir, 'loader.cjs.js');
const polyfillsExport = `export * from '${relative(loaderPath, polyfillsEntryPoint)}';`;
const indexContent = `${generatePreamble(config)}
${es5HtmlElement}
${polyfillsExport}
export * from '${relative(loaderPath, es5EntryPoint)}';
`;
const indexES2017Content = `${generatePreamble(config)}
${polyfillsExport}
export * from '${relative(loaderPath, es2017EntryPoint)}';
`;
const indexCjsContent = `${generatePreamble(config)}
module.exports = require('${relative(loaderPath, cjsEntryPoint)}');
module.exports.applyPolyfills = function() { return Promise.resolve() };
`;
const indexCjsContent = filterAndJoin([
generatePreamble(config),
`module.exports = require('${relative(loaderPath, cjsEntryPoint)}');`,
config.buildEs5 ? `module.exports.applyPolyfills = function() { return Promise.resolve() };` : null,
]);

const indexDtsPath = join(loaderPath, 'index.d.ts');

await Promise.all([
compilerCtx.fs.writeFile(join(loaderPath, 'package.json'), packageJsonContent),
compilerCtx.fs.writeFile(join(loaderPath, 'index.d.ts'), generateIndexDts(indexDtsPath, outputTarget.componentDts)),
Expand Down Expand Up @@ -98,3 +105,18 @@ export declare function applyPolyfills(): Promise<void>;
export declare function setNonce(nonce: string): void;
`;
};

/**
* Given an array of 'parts' which can be assembled into a string 1) filter
* out any parts that are `null` and 2) join the remaining strings into a single
* output string
*
* @param parts an array of parts to filter and join
* @returns the joined string
*/
function filterAndJoin(parts: (string | null)[]): string {
return parts
.filter((part) => part !== null)
.join('\n')
.trim();
}
73 changes: 73 additions & 0 deletions src/compiler/output-targets/test/output-lazy-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type * as d from '@stencil/core/declarations';
import { mockBuildCtx, mockCompilerCtx, mockCompilerSystem, mockValidatedConfig } from '@stencil/core/testing';
import { DIST, resolve } from '@utils';

import { validateDist } from '../../config/outputs/validate-dist';
import { outputLazyLoader } from '../output-lazy-loader';

function setup(configOverrides: Partial<d.ValidatedConfig> = {}) {
const sys = mockCompilerSystem();
const config: d.ValidatedConfig = mockValidatedConfig({
...configOverrides,
configPath: '/testing-path',
buildAppCore: true,
namespace: 'TestApp',
outputTargets: [
{
type: DIST,
dir: 'my-test-dir',
},
],
srcDir: '/src',
sys,
});

config.outputTargets = validateDist(config, config.outputTargets);

const compilerCtx = mockCompilerCtx(config);
const writeFileSpy = jest.spyOn(compilerCtx.fs, 'writeFile');
const buildCtx = mockBuildCtx(config, compilerCtx);

return { config, compilerCtx, buildCtx, writeFileSpy };
}

describe('Lazy Loader Output Target', () => {
let config: d.ValidatedConfig;
let compilerCtx: d.CompilerCtx;
let writeFileSpy: jest.SpyInstance;

afterEach(() => {
writeFileSpy.mockRestore();
});

it('should write code for initializing polyfills when buildEs5=true', async () => {
({ config, compilerCtx, writeFileSpy } = setup({ buildEs5: true }));
await outputLazyLoader(config, compilerCtx);

const expectedIndexOutput = `export * from '../esm/polyfills/index.js';
export * from '../esm-es5/loader.js';`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.js'), expectedIndexOutput);

const expectedCjsIndexOutput = `module.exports = require('../cjs/loader.cjs.js');
module.exports.applyPolyfills = function() { return Promise.resolve() };`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.cjs.js'), expectedCjsIndexOutput);

const expectedES2017Output = `export * from '../esm/polyfills/index.js';
export * from '../esm/loader.js';`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.es2017.js'), expectedES2017Output);
});

it('should exclude polyfill code when buildEs5=false', async () => {
({ config, compilerCtx, writeFileSpy } = setup({ buildEs5: false }));
await outputLazyLoader(config, compilerCtx);

const expectedIndexOutput = `export * from '../esm/loader.js';`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.js'), expectedIndexOutput);

const expectedCjsIndexOutput = `module.exports = require('../cjs/loader.cjs.js');`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.cjs.js'), expectedCjsIndexOutput);

const expectedES2017Output = `export * from '../esm/loader.js';`;
expect(writeFileSpy).toHaveBeenCalledWith(resolve('/my-test-dir/loader/index.es2017.js'), expectedES2017Output);
});
});

0 comments on commit 945df46

Please sign in to comment.