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
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 47c8efc
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 47c8efc

Please sign in to comment.