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 2, 2024
1 parent 6723e30 commit 637a3da
Show file tree
Hide file tree
Showing 3 changed files with 123 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();
}
74 changes: 74 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,74 @@
import type * as d from '@stencil/core/declarations';

Check failure on line 1 in src/compiler/output-targets/test/output-lazy-loader.spec.ts

View workflow job for this annotation

GitHub Actions / Lint and Format / Check

Run autofix to sort these imports!
import { mockBuildCtx, mockCompilerCtx, mockCompilerSystem, mockValidatedConfig } from '@stencil/core/testing';
import { DIST } from '@utils';

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

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(normalizePath('/my-test-dir/loader/index.js'), expectedIndexOutput);

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

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

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(normalizePath('/my-test-dir/loader/index.js'), expectedIndexOutput);

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

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

0 comments on commit 637a3da

Please sign in to comment.