Skip to content

Commit

Permalink
feat(compiler): export all built components from index.js w/ dist-cus…
Browse files Browse the repository at this point in the history
…tom-elements

This makes a change to the `dist-custom-elements` output target code
which adds exports from the generated `index.js` file for all of the
components included in the build. This should help to enable Stencil
users to migrate away from the `dist-custom-elements-bundle` output
target, which we're planning to remove in the future (see #3136 for
details and discussion on that).

In order to enable this we don't need to make a particularly large
change. The index chunk which we generate and pass to Rollup is just
amended to include some references to the other modules we declare (one
per each component), and Rollup takes care of resolving that into
actual, bundled concrete modules.

As part of making this change there is also a new test file added to
exercise the functions for the `dist-custom-elements` output target,
which necessitated improving our mocks a little bit. This should help us
to test the rest of the output target code in the future.

STENCIL-332 Investigate re-exporting components via index.js
  • Loading branch information
alicewriteswrongs authored and rwaskiewicz committed Jun 13, 2022
1 parent 1ae228d commit ff0e8cc
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 35 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
moduleNameMapper: {
'@app-data': '<rootDir>/internal/app-data/index.cjs',
'@app-globals': '<rootDir>/internal/app-globals/index.cjs',
'@compiler-deps': '<rootDir>/src/compiler/sys/modules/compiler-deps.ts',
'@platform': '<rootDir>/internal/testing/index.js',
'@runtime': '<rootDir>/internal/testing/index.js',
'@stencil/core/cli': '<rootDir>/cli/index.js',
Expand Down
22 changes: 22 additions & 0 deletions src/compiler/bundle/bundle-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,31 @@ export interface BundleOptions {
externalRuntime?: boolean;
platform: 'client' | 'hydrate' | 'worker';
customTransformers?: TransformerFactory<SourceFile>[];
/**
* This is equivalent to the Rollup `input` configuration option. It's
* an object mapping names to entry points which tells Rollup to bundle
* each thing up as a separate output chunk.
*
* @see {@link https://rollupjs.org/guide/en/#input}
*/
inputs: { [entryKey: string]: string };
/**
* A map of strings which are passed to the Stencil-specific loader plugin
* which we use to resolve the imports of Stencil project files when building
* with Rollup.
*
* @see {@link loader-plugin:loaderPlugin}
*/
loader?: { [id: string]: string };
inlineDynamicImports?: boolean;
inlineWorkers?: boolean;
/**
* Duplicate of Rollup's `preserveEntrySignatures` option.
*
* "Controls if Rollup tries to ensure that entry chunks have the same
* exports as the underlying entry module."
*
* @see {@link https://rollupjs.org/guide/en/#preserveentrysignatures}
*/
preserveEntrySignatures?: PreserveEntrySignaturesOption;
}
1 change: 1 addition & 0 deletions src/compiler/bundle/loader-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { LoadResult, Plugin, ResolveIdResult } from 'rollup';
* using the `dist-custom-elements` output target may have a single 'entry point' for each file containing a component.
* Each of those files will be independently resolved and loaded by this plugin for further processing by Rollup later
* in the bundling process.
*
* @param entries the Stencil project files to process. It should be noted that the keys in this object may not
* necessarily be an absolute or relative path to a file, but may be a Rollup Virtual Module (which begin with \0).
* @returns the rollup plugin that loads and process a Stencil project's entry points
Expand Down
137 changes: 105 additions & 32 deletions src/compiler/output-targets/dist-custom-elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
generatePreamble,
getSourceMappingUrlForEndOfFile,
hasError,
isString,
rollupToStencilSourceMap,
} from '@utils';
import { getCustomElementsBuildConditionals } from '../dist-custom-elements-bundle/custom-elements-build-conditionals';
Expand All @@ -21,6 +22,17 @@ import { proxyCustomElement } from '../../transformers/component-native/proxy-cu
import { updateStencilCoreImports } from '../../transformers/update-stencil-core-import';
import ts from 'typescript';

/**
* Main output target function for `dist-custom-elements`. This function just
* does some organizational work to call the other functions in this module,
* which do actual work of generating the rollup configuration, creating an
* entry chunk, running, the build, etc.
*
* @param config the user-supplied compiler configuration we're using
* @param compilerCtx the current compiler context
* @param buildCtx the current build context
* @returns an empty Promise which won't resolve until the work is done!
*/
export const outputCustomElements = async (
config: d.Config,
compilerCtx: d.CompilerCtx,
Expand All @@ -30,46 +42,80 @@ export const outputCustomElements = async (
return;
}

const outputTargets = config.outputTargets.filter(isOutputTargetDistCustomElements);
const outputTargets = (config.outputTargets ?? []).filter(isOutputTargetDistCustomElements);
if (outputTargets.length === 0) {
return;
}

const bundlingEventMessage = 'generate custom elements';
const timespan = buildCtx.createTimeSpan(`${bundlingEventMessage} started`);

await Promise.all(outputTargets.map((o) => bundleCustomElements(config, compilerCtx, buildCtx, o)));
await Promise.all(outputTargets.map((target) => bundleCustomElements(config, compilerCtx, buildCtx, target)));

timespan.finish(`${bundlingEventMessage} finished`);
};

const bundleCustomElements = async (
/**
* Get bundle options for our current build and compiler context which we'll use
* to generate a Rollup build and so on.
*
* @param config user-supplied Stencil configuration
* @param buildCtx the current build context
* @param compilerCtx the current compiler context
* @param outputTarget the outputTarget we're currently dealing with
* @returns bundle options suitable for generating a rollup configuration
*/
export const getBundleOptions = (
config: d.Config,
buildCtx: d.BuildCtx,
compilerCtx: d.CompilerCtx,
outputTarget: d.OutputTargetDistCustomElements
): BundleOptions => ({
id: 'customElements',
platform: 'client',
conditionals: getCustomElementsBuildConditionals(config, buildCtx.components),
customTransformers: getCustomElementCustomTransformer(config, compilerCtx, buildCtx.components, outputTarget),
externalRuntime: !!outputTarget.externalRuntime,
inlineWorkers: true,
inputs: {
// Here we prefix our index chunk with '\0' to tell Rollup that we're
// going to be using virtual modules with this module. A leading '\0'
// prevents other plugins from messing with the module. We generate a
// string for the index chunk below in the `loader` property.
//
// @see {@link https://rollupjs.org/guide/en/#conventions} for more info.
index: '\0core',
},
loader: {
'\0core': generateEntryPoint(outputTarget),
},
inlineDynamicImports: outputTarget.inlineDynamicImports,
preserveEntrySignatures: 'allow-extension',
});

/**
* Get bundle options for rollup, run the rollup build, optionally minify the
* output, and write files to disk.
* @param config user-supplied Stencil configuration
* @param buildCtx the current build context
* @param compilerCtx the current compiler context
* @param outputTarget the outputTarget we're currently dealing with
* @returns an empty promise
*/

export const bundleCustomElements = async (
config: d.Config,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
outputTarget: d.OutputTargetDistCustomElements
) => {
try {
const bundleOpts: BundleOptions = {
id: 'customElements',
platform: 'client',
conditionals: getCustomElementsBuildConditionals(config, buildCtx.components),
customTransformers: getCustomElementCustomTransformer(config, compilerCtx, buildCtx.components, outputTarget),
externalRuntime: !!outputTarget.externalRuntime,
inlineWorkers: true,
inputs: {
index: '\0core',
},
loader: {
'\0core': generateEntryPoint(outputTarget),
},
inlineDynamicImports: outputTarget.inlineDynamicImports,
preserveEntrySignatures: 'allow-extension',
};
const bundleOpts = getBundleOptions(config, buildCtx, compilerCtx, outputTarget);

addCustomElementInputs(buildCtx, bundleOpts);

const build = await bundleOutput(config, compilerCtx, buildCtx, bundleOpts);

if (build) {
const rollupOutput = await build.generate({
banner: generatePreamble(config),
Expand All @@ -81,6 +127,20 @@ const bundleCustomElements = async (
preferConst: true,
});

// the output target should have been validated at this point - as a result, we expect this field
// to have been backfilled if it wasn't provided
const outputTargetDir: string = outputTarget.dir!;

// besides, if it isn't here we do a diagnostic and an early return
if (!isString(outputTargetDir)) {
buildCtx.diagnostics.push({
level: 'error',
type: 'build',
messageText: 'dist-custom-elements output target provided with no output target directory!',
});
return;
}

const minify = outputTarget.externalRuntime || outputTarget.minify !== true ? false : config.minifyJs;
const files = rollupOutput.output.map(async (bundle) => {
if (bundle.type === 'chunk') {
Expand All @@ -96,19 +156,15 @@ const bundleCustomElements = async (
buildCtx.diagnostics.push(...optimizeResults.diagnostics);
if (!hasError(optimizeResults.diagnostics) && typeof optimizeResults.output === 'string') {
code = optimizeResults.output;
sourceMap = optimizeResults.sourceMap;
}
if (sourceMap) {
if (optimizeResults.sourceMap) {
sourceMap = optimizeResults.sourceMap;
code = code + getSourceMappingUrlForEndOfFile(bundle.fileName);
await compilerCtx.fs.writeFile(
join(outputTarget.dir, bundle.fileName + '.map'),
JSON.stringify(sourceMap),
{
outputTargetType: outputTarget.type,
}
);
await compilerCtx.fs.writeFile(join(outputTargetDir, bundle.fileName + '.map'), JSON.stringify(sourceMap), {
outputTargetType: outputTarget.type,
});
}
await compilerCtx.fs.writeFile(join(outputTarget.dir, bundle.fileName), code, {
await compilerCtx.fs.writeFile(join(outputTargetDir, bundle.fileName), code, {
outputTargetType: outputTarget.type,
});
}
Expand All @@ -125,8 +181,11 @@ const bundleCustomElements = async (
* @param buildCtx the context for the current build
* @param bundleOpts the bundle options to store the virtual modules under. acts as an output parameter
*/
const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions): void => {
export const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions): void => {
const components = buildCtx.components;
// an array to store the imports of these modules that we're going to add to our entry chunk
const indexImports: string[] = [];

components.forEach((cmp) => {
const exp: string[] = [];
const exportName = dashToPascalCase(cmp.tagName);
Expand All @@ -136,26 +195,39 @@ const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions)

if (cmp.isPlain) {
exp.push(`export { ${importName} as ${exportName} } from '${cmp.sourceFilePath}';`);
indexImports.push(`export { {${exportName} } from '${coreKey}';`);
} else {
// the `importName` may collide with the `exportName`, alias it just in case it does with `importAs`
exp.push(
`import { ${importName} as ${importAs}, defineCustomElement as cmpDefCustomEle } from '${cmp.sourceFilePath}';`
);
exp.push(`export const ${exportName} = ${importAs};`);
exp.push(`export const defineCustomElement = cmpDefCustomEle;`);

// Here we push an export (with a rename for `defineCustomElement` for
// this component onto our array which references the `coreKey` (prefixed
// with `\0`). We have to do this so that our import is referencing the
// correct virtual module, if we instead referenced, for instance,
// `cmp.sourceFilePath`, we would end up with duplicated modules in our
// output.
indexImports.push(
`export { ${exportName}, defineCustomElement as defineCustomElement${exportName} } from '${coreKey}';`
);
}

bundleOpts.inputs[cmp.tagName] = coreKey;
bundleOpts.loader[coreKey] = exp.join('\n');
bundleOpts.loader![coreKey] = exp.join('\n');
});

bundleOpts.loader!['\0core'] += indexImports.join('\n');
};

/**
* Generate the entrypoint (`index.ts` file) contents for the `dist-custom-elements` output target
* @param outputTarget the output target's configuration
* @returns the stringified contents to be placed in the entrypoint
*/
const generateEntryPoint = (outputTarget: d.OutputTargetDistCustomElements): string => {
export const generateEntryPoint = (outputTarget: d.OutputTargetDistCustomElements): string => {
const imp: string[] = [];

imp.push(
Expand All @@ -173,6 +245,7 @@ const generateEntryPoint = (outputTarget: d.OutputTargetDistCustomElements): str
/**
* Get the series of custom transformers that will be applied to a Stencil project's source code during the TypeScript
* transpilation process
*
* @param config the configuration for the Stencil project
* @param compilerCtx the current compiler context
* @param components the components that will be compiled as a part of the current build
Expand Down
Loading

0 comments on commit ff0e8cc

Please sign in to comment.