Skip to content

Commit ff0e8cc

Browse files
alicewriteswrongsrwaskiewicz
authored andcommittedJun 13, 2022
feat(compiler): export all built components from index.js w/ dist-custom-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 stenciljs#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
1 parent 1ae228d commit ff0e8cc

File tree

9 files changed

+364
-35
lines changed

9 files changed

+364
-35
lines changed
 

‎jest.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module.exports = {
33
moduleNameMapper: {
44
'@app-data': '<rootDir>/internal/app-data/index.cjs',
55
'@app-globals': '<rootDir>/internal/app-globals/index.cjs',
6+
'@compiler-deps': '<rootDir>/src/compiler/sys/modules/compiler-deps.ts',
67
'@platform': '<rootDir>/internal/testing/index.js',
78
'@runtime': '<rootDir>/internal/testing/index.js',
89
'@stencil/core/cli': '<rootDir>/cli/index.js',

‎src/compiler/bundle/bundle-interface.ts

+22
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,31 @@ export interface BundleOptions {
88
externalRuntime?: boolean;
99
platform: 'client' | 'hydrate' | 'worker';
1010
customTransformers?: TransformerFactory<SourceFile>[];
11+
/**
12+
* This is equivalent to the Rollup `input` configuration option. It's
13+
* an object mapping names to entry points which tells Rollup to bundle
14+
* each thing up as a separate output chunk.
15+
*
16+
* @see {@link https://rollupjs.org/guide/en/#input}
17+
*/
1118
inputs: { [entryKey: string]: string };
19+
/**
20+
* A map of strings which are passed to the Stencil-specific loader plugin
21+
* which we use to resolve the imports of Stencil project files when building
22+
* with Rollup.
23+
*
24+
* @see {@link loader-plugin:loaderPlugin}
25+
*/
1226
loader?: { [id: string]: string };
1327
inlineDynamicImports?: boolean;
1428
inlineWorkers?: boolean;
29+
/**
30+
* Duplicate of Rollup's `preserveEntrySignatures` option.
31+
*
32+
* "Controls if Rollup tries to ensure that entry chunks have the same
33+
* exports as the underlying entry module."
34+
*
35+
* @see {@link https://rollupjs.org/guide/en/#preserveentrysignatures}
36+
*/
1537
preserveEntrySignatures?: PreserveEntrySignaturesOption;
1638
}

‎src/compiler/bundle/loader-plugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { LoadResult, Plugin, ResolveIdResult } from 'rollup';
55
* using the `dist-custom-elements` output target may have a single 'entry point' for each file containing a component.
66
* Each of those files will be independently resolved and loaded by this plugin for further processing by Rollup later
77
* in the bundling process.
8+
*
89
* @param entries the Stencil project files to process. It should be noted that the keys in this object may not
910
* necessarily be an absolute or relative path to a file, but may be a Rollup Virtual Module (which begin with \0).
1011
* @returns the rollup plugin that loads and process a Stencil project's entry points

‎src/compiler/output-targets/dist-custom-elements/index.ts

+105-32
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
generatePreamble,
88
getSourceMappingUrlForEndOfFile,
99
hasError,
10+
isString,
1011
rollupToStencilSourceMap,
1112
} from '@utils';
1213
import { getCustomElementsBuildConditionals } from '../dist-custom-elements-bundle/custom-elements-build-conditionals';
@@ -21,6 +22,17 @@ import { proxyCustomElement } from '../../transformers/component-native/proxy-cu
2122
import { updateStencilCoreImports } from '../../transformers/update-stencil-core-import';
2223
import ts from 'typescript';
2324

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

33-
const outputTargets = config.outputTargets.filter(isOutputTargetDistCustomElements);
45+
const outputTargets = (config.outputTargets ?? []).filter(isOutputTargetDistCustomElements);
3446
if (outputTargets.length === 0) {
3547
return;
3648
}
3749

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

41-
await Promise.all(outputTargets.map((o) => bundleCustomElements(config, compilerCtx, buildCtx, o)));
53+
await Promise.all(outputTargets.map((target) => bundleCustomElements(config, compilerCtx, buildCtx, target)));
4254

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

46-
const bundleCustomElements = async (
58+
/**
59+
* Get bundle options for our current build and compiler context which we'll use
60+
* to generate a Rollup build and so on.
61+
*
62+
* @param config user-supplied Stencil configuration
63+
* @param buildCtx the current build context
64+
* @param compilerCtx the current compiler context
65+
* @param outputTarget the outputTarget we're currently dealing with
66+
* @returns bundle options suitable for generating a rollup configuration
67+
*/
68+
export const getBundleOptions = (
69+
config: d.Config,
70+
buildCtx: d.BuildCtx,
71+
compilerCtx: d.CompilerCtx,
72+
outputTarget: d.OutputTargetDistCustomElements
73+
): BundleOptions => ({
74+
id: 'customElements',
75+
platform: 'client',
76+
conditionals: getCustomElementsBuildConditionals(config, buildCtx.components),
77+
customTransformers: getCustomElementCustomTransformer(config, compilerCtx, buildCtx.components, outputTarget),
78+
externalRuntime: !!outputTarget.externalRuntime,
79+
inlineWorkers: true,
80+
inputs: {
81+
// Here we prefix our index chunk with '\0' to tell Rollup that we're
82+
// going to be using virtual modules with this module. A leading '\0'
83+
// prevents other plugins from messing with the module. We generate a
84+
// string for the index chunk below in the `loader` property.
85+
//
86+
// @see {@link https://rollupjs.org/guide/en/#conventions} for more info.
87+
index: '\0core',
88+
},
89+
loader: {
90+
'\0core': generateEntryPoint(outputTarget),
91+
},
92+
inlineDynamicImports: outputTarget.inlineDynamicImports,
93+
preserveEntrySignatures: 'allow-extension',
94+
});
95+
96+
/**
97+
* Get bundle options for rollup, run the rollup build, optionally minify the
98+
* output, and write files to disk.
99+
* @param config user-supplied Stencil configuration
100+
* @param buildCtx the current build context
101+
* @param compilerCtx the current compiler context
102+
* @param outputTarget the outputTarget we're currently dealing with
103+
* @returns an empty promise
104+
*/
105+
106+
export const bundleCustomElements = async (
47107
config: d.Config,
48108
compilerCtx: d.CompilerCtx,
49109
buildCtx: d.BuildCtx,
50110
outputTarget: d.OutputTargetDistCustomElements
51111
) => {
52112
try {
53-
const bundleOpts: BundleOptions = {
54-
id: 'customElements',
55-
platform: 'client',
56-
conditionals: getCustomElementsBuildConditionals(config, buildCtx.components),
57-
customTransformers: getCustomElementCustomTransformer(config, compilerCtx, buildCtx.components, outputTarget),
58-
externalRuntime: !!outputTarget.externalRuntime,
59-
inlineWorkers: true,
60-
inputs: {
61-
index: '\0core',
62-
},
63-
loader: {
64-
'\0core': generateEntryPoint(outputTarget),
65-
},
66-
inlineDynamicImports: outputTarget.inlineDynamicImports,
67-
preserveEntrySignatures: 'allow-extension',
68-
};
113+
const bundleOpts = getBundleOptions(config, buildCtx, compilerCtx, outputTarget);
69114

70115
addCustomElementInputs(buildCtx, bundleOpts);
71116

72117
const build = await bundleOutput(config, compilerCtx, buildCtx, bundleOpts);
118+
73119
if (build) {
74120
const rollupOutput = await build.generate({
75121
banner: generatePreamble(config),
@@ -81,6 +127,20 @@ const bundleCustomElements = async (
81127
preferConst: true,
82128
});
83129

130+
// the output target should have been validated at this point - as a result, we expect this field
131+
// to have been backfilled if it wasn't provided
132+
const outputTargetDir: string = outputTarget.dir!;
133+
134+
// besides, if it isn't here we do a diagnostic and an early return
135+
if (!isString(outputTargetDir)) {
136+
buildCtx.diagnostics.push({
137+
level: 'error',
138+
type: 'build',
139+
messageText: 'dist-custom-elements output target provided with no output target directory!',
140+
});
141+
return;
142+
}
143+
84144
const minify = outputTarget.externalRuntime || outputTarget.minify !== true ? false : config.minifyJs;
85145
const files = rollupOutput.output.map(async (bundle) => {
86146
if (bundle.type === 'chunk') {
@@ -96,19 +156,15 @@ const bundleCustomElements = async (
96156
buildCtx.diagnostics.push(...optimizeResults.diagnostics);
97157
if (!hasError(optimizeResults.diagnostics) && typeof optimizeResults.output === 'string') {
98158
code = optimizeResults.output;
99-
sourceMap = optimizeResults.sourceMap;
100159
}
101-
if (sourceMap) {
160+
if (optimizeResults.sourceMap) {
161+
sourceMap = optimizeResults.sourceMap;
102162
code = code + getSourceMappingUrlForEndOfFile(bundle.fileName);
103-
await compilerCtx.fs.writeFile(
104-
join(outputTarget.dir, bundle.fileName + '.map'),
105-
JSON.stringify(sourceMap),
106-
{
107-
outputTargetType: outputTarget.type,
108-
}
109-
);
163+
await compilerCtx.fs.writeFile(join(outputTargetDir, bundle.fileName + '.map'), JSON.stringify(sourceMap), {
164+
outputTargetType: outputTarget.type,
165+
});
110166
}
111-
await compilerCtx.fs.writeFile(join(outputTarget.dir, bundle.fileName), code, {
167+
await compilerCtx.fs.writeFile(join(outputTargetDir, bundle.fileName), code, {
112168
outputTargetType: outputTarget.type,
113169
});
114170
}
@@ -125,8 +181,11 @@ const bundleCustomElements = async (
125181
* @param buildCtx the context for the current build
126182
* @param bundleOpts the bundle options to store the virtual modules under. acts as an output parameter
127183
*/
128-
const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions): void => {
184+
export const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions): void => {
129185
const components = buildCtx.components;
186+
// an array to store the imports of these modules that we're going to add to our entry chunk
187+
const indexImports: string[] = [];
188+
130189
components.forEach((cmp) => {
131190
const exp: string[] = [];
132191
const exportName = dashToPascalCase(cmp.tagName);
@@ -136,26 +195,39 @@ const addCustomElementInputs = (buildCtx: d.BuildCtx, bundleOpts: BundleOptions)
136195

137196
if (cmp.isPlain) {
138197
exp.push(`export { ${importName} as ${exportName} } from '${cmp.sourceFilePath}';`);
198+
indexImports.push(`export { {${exportName} } from '${coreKey}';`);
139199
} else {
140200
// the `importName` may collide with the `exportName`, alias it just in case it does with `importAs`
141201
exp.push(
142202
`import { ${importName} as ${importAs}, defineCustomElement as cmpDefCustomEle } from '${cmp.sourceFilePath}';`
143203
);
144204
exp.push(`export const ${exportName} = ${importAs};`);
145205
exp.push(`export const defineCustomElement = cmpDefCustomEle;`);
206+
207+
// Here we push an export (with a rename for `defineCustomElement` for
208+
// this component onto our array which references the `coreKey` (prefixed
209+
// with `\0`). We have to do this so that our import is referencing the
210+
// correct virtual module, if we instead referenced, for instance,
211+
// `cmp.sourceFilePath`, we would end up with duplicated modules in our
212+
// output.
213+
indexImports.push(
214+
`export { ${exportName}, defineCustomElement as defineCustomElement${exportName} } from '${coreKey}';`
215+
);
146216
}
147217

148218
bundleOpts.inputs[cmp.tagName] = coreKey;
149-
bundleOpts.loader[coreKey] = exp.join('\n');
219+
bundleOpts.loader![coreKey] = exp.join('\n');
150220
});
221+
222+
bundleOpts.loader!['\0core'] += indexImports.join('\n');
151223
};
152224

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

161233
imp.push(
@@ -173,6 +245,7 @@ const generateEntryPoint = (outputTarget: d.OutputTargetDistCustomElements): str
173245
/**
174246
* Get the series of custom transformers that will be applied to a Stencil project's source code during the TypeScript
175247
* transpilation process
248+
*
176249
* @param config the configuration for the Stencil project
177250
* @param compilerCtx the current compiler context
178251
* @param components the components that will be compiled as a part of the current build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { path } from '@stencil/core/compiler';
2+
import { mockConfig, mockStencilSystem, mockBuildCtx, mockCompilerCtx, mockModule } from '@stencil/core/testing';
3+
import type * as d from '../../../declarations';
4+
import {
5+
addCustomElementInputs,
6+
bundleCustomElements,
7+
generateEntryPoint,
8+
getBundleOptions,
9+
outputCustomElements,
10+
} from '../dist-custom-elements';
11+
import * as outputCustomElementsMod from '../dist-custom-elements';
12+
import { OutputTargetDistCustomElements } from '../../../declarations';
13+
import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub';
14+
import { STENCIL_APP_GLOBALS_ID, STENCIL_INTERNAL_CLIENT_ID, USER_INDEX_ENTRY_ID } from '../../bundle/entry-alias-ids';
15+
import { DIST_CUSTOM_ELEMENTS, DIST_CUSTOM_ELEMENTS_BUNDLE } from '../output-utils';
16+
17+
const setup = () => {
18+
const sys = mockStencilSystem();
19+
const config: d.Config = mockConfig(sys);
20+
const compilerCtx = mockCompilerCtx(config);
21+
const buildCtx = mockBuildCtx(config, compilerCtx);
22+
const root = config.rootDir;
23+
config.configPath = '/testing-path';
24+
config.srcDir = '/src';
25+
config.buildAppCore = true;
26+
config.rootDir = path.join(root, 'User', 'testing', '/');
27+
config.namespace = 'TestApp';
28+
config.buildEs5 = true;
29+
config.globalScript = path.join(root, 'User', 'testing', 'src', 'global.ts');
30+
config.outputTargets = [{ type: DIST_CUSTOM_ELEMENTS }];
31+
32+
const bundleCustomElementsSpy = jest.spyOn(outputCustomElementsMod, 'bundleCustomElements');
33+
34+
compilerCtx.moduleMap.set('test', mockModule());
35+
36+
return { config, compilerCtx, buildCtx, bundleCustomElementsSpy };
37+
};
38+
39+
describe('Custom Elements output target', () => {
40+
it('should return early if config.buildDist is false', async () => {
41+
const { config, compilerCtx, buildCtx, bundleCustomElementsSpy } = setup();
42+
config.buildDist = false;
43+
await outputCustomElements(config, compilerCtx, buildCtx);
44+
expect(bundleCustomElementsSpy).not.toHaveBeenCalled();
45+
});
46+
47+
it.each<d.OutputTarget[][]>([
48+
[[]],
49+
[[{ type: 'dist' }]],
50+
[[{ type: 'dist' }, { type: DIST_CUSTOM_ELEMENTS_BUNDLE }]],
51+
])('should return early if no appropriate output target (%j)', async (outputTargets) => {
52+
const { config, compilerCtx, buildCtx, bundleCustomElementsSpy } = setup();
53+
config.outputTargets = outputTargets;
54+
await outputCustomElements(config, compilerCtx, buildCtx);
55+
expect(bundleCustomElementsSpy).not.toHaveBeenCalled();
56+
});
57+
58+
describe('generateEntryPoint', () => {
59+
it.each([true, false])('should include globalScripts if the right option is set', (includeGlobalScripts) => {
60+
const entryPoint = generateEntryPoint({
61+
type: DIST_CUSTOM_ELEMENTS,
62+
includeGlobalScripts,
63+
});
64+
const globalScriptsBoilerplate = `import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';\nglobalScripts();`;
65+
expect(entryPoint.includes(globalScriptsBoilerplate)).toBe(includeGlobalScripts);
66+
});
67+
});
68+
69+
describe('getBundleOptions', () => {
70+
it('should set basic properties on BundleOptions', () => {
71+
const { config, buildCtx, compilerCtx } = setup();
72+
const options = getBundleOptions(config, buildCtx, compilerCtx, { type: DIST_CUSTOM_ELEMENTS });
73+
expect(options.id).toBe('customElements');
74+
expect(options.platform).toBe('client');
75+
expect(options.inlineWorkers).toBe(true);
76+
expect(options.inputs).toEqual({
77+
index: '\0core',
78+
});
79+
expect(options.loader).toEqual({
80+
'\0core': generateEntryPoint({ type: DIST_CUSTOM_ELEMENTS }),
81+
});
82+
expect(options.preserveEntrySignatures).toEqual('allow-extension');
83+
});
84+
85+
it.each([true, false, undefined])('should set externalRuntime correctly when %p', (externalRuntime) => {
86+
const { config, buildCtx, compilerCtx } = setup();
87+
const options = getBundleOptions(config, buildCtx, compilerCtx, {
88+
type: DIST_CUSTOM_ELEMENTS,
89+
externalRuntime,
90+
});
91+
if (externalRuntime) {
92+
expect(options.externalRuntime).toBe(true);
93+
} else {
94+
expect(options.externalRuntime).toBe(false);
95+
}
96+
});
97+
98+
it.each([true, false, undefined])('should pass through inlineDynamicImports=%p', (inlineDynamicImports) => {
99+
const { config, buildCtx, compilerCtx } = setup();
100+
const options = getBundleOptions(config, buildCtx, compilerCtx, {
101+
type: DIST_CUSTOM_ELEMENTS,
102+
inlineDynamicImports,
103+
});
104+
expect(options.inlineDynamicImports).toBe(inlineDynamicImports);
105+
});
106+
});
107+
108+
describe('bundleCustomElements', () => {
109+
it('should set a diagnostic if no `dir` prop on the output target', async () => {
110+
const { config, compilerCtx, buildCtx } = setup();
111+
const outputTarget: OutputTargetDistCustomElements = { type: DIST_CUSTOM_ELEMENTS };
112+
await bundleCustomElements(config, compilerCtx, buildCtx, outputTarget);
113+
expect(buildCtx.diagnostics).toEqual([
114+
{
115+
level: 'error',
116+
type: 'build',
117+
messageText: 'dist-custom-elements output target provided with no output target directory!',
118+
},
119+
]);
120+
});
121+
});
122+
123+
describe('addCustomElementInputs', () => {
124+
it('should add imports to index.js for all included components', () => {
125+
const componentOne = stubComponentCompilerMeta();
126+
const componentTwo = stubComponentCompilerMeta({
127+
componentClassName: 'MyBestComponent',
128+
tagName: 'my-best-component',
129+
});
130+
const { config, compilerCtx, buildCtx } = setup();
131+
buildCtx.components = [componentOne, componentTwo];
132+
133+
const bundleOptions = getBundleOptions(
134+
config,
135+
buildCtx,
136+
compilerCtx,
137+
config.outputTargets[0] as OutputTargetDistCustomElements
138+
);
139+
addCustomElementInputs(buildCtx, bundleOptions);
140+
expect(bundleOptions.loader['\0core']).toEqual(
141+
`export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
142+
export * from '${USER_INDEX_ENTRY_ID}';
143+
import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
144+
globalScripts();
145+
export { StubCmp, defineCustomElement as defineCustomElementStubCmp } from '\0StubCmp';
146+
export { MyBestComponent, defineCustomElement as defineCustomElementMyBestComponent } from '\0MyBestComponent';`
147+
);
148+
});
149+
150+
it('should correctly handle capitalization edge-cases', () => {
151+
const component = stubComponentCompilerMeta({
152+
componentClassName: 'ComponentWithJSX',
153+
tagName: 'component-with-jsx',
154+
});
155+
156+
const { config, compilerCtx, buildCtx } = setup();
157+
buildCtx.components = [component];
158+
159+
const bundleOptions = getBundleOptions(
160+
config,
161+
buildCtx,
162+
compilerCtx,
163+
config.outputTargets[0] as OutputTargetDistCustomElements
164+
);
165+
addCustomElementInputs(buildCtx, bundleOptions);
166+
expect(bundleOptions.loader['\0core']).toEqual(
167+
`export { setAssetPath, setPlatformOptions } from '${STENCIL_INTERNAL_CLIENT_ID}';
168+
export * from '${USER_INDEX_ENTRY_ID}';
169+
import { globalScripts } from '${STENCIL_APP_GLOBALS_ID}';
170+
globalScripts();
171+
export { ComponentWithJsx, defineCustomElement as defineCustomElementComponentWithJsx } from '\0ComponentWithJsx';`
172+
);
173+
});
174+
});
175+
});

‎src/compiler/transformers/update-stencil-core-import.ts

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export const updateStencilCoreImports = (updatedCoreImportPath: string): ts.Tran
6969
};
7070
};
7171

72+
/**
73+
* A set of imports which we don't want to remove from an output file
74+
*/
7275
const KEEP_IMPORTS = new Set([
7376
'h',
7477
'setMode',

‎src/testing/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export {
1111
mockLogger,
1212
mockStencilSystem,
1313
mockWindow,
14+
mockModule,
1415
} from './mocks';
1516
export {
1617
MockHeaders,

‎src/testing/mocks.ts

+54-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { BuildCtx, Cache, CompilerCtx, CompilerSystem, Config } from '@stencil/core/internal';
1+
import type { BuildCtx, Cache, CompilerCtx, CompilerSystem, Config, Module } from '@stencil/core/internal';
22
import { BuildContext } from '../compiler/build/build-ctx';
33
import { Cache as CompilerCache } from '../compiler/cache';
44
import { createInMemoryFs } from '../compiler/sys/in-memory-fs';
@@ -8,6 +8,7 @@ import { MockWindow } from '@stencil/core/mock-doc';
88
import { TestingLogger } from './testing-logger';
99
import path from 'path';
1010
import { noop } from '@utils';
11+
import { buildEvents } from '../compiler/events';
1112

1213
export function mockConfig(sys?: CompilerSystem) {
1314
const rootDir = path.resolve('/');
@@ -44,6 +45,10 @@ export function mockConfig(sys?: CompilerSystem) {
4445
customResolveOptions: {},
4546
},
4647
sourceMap: true,
48+
rollupPlugins: {
49+
before: [],
50+
after: [],
51+
},
4752
};
4853

4954
return config;
@@ -70,7 +75,7 @@ export function mockCompilerCtx(config?: Config) {
7075
compilerOptions: null,
7176
cache: null,
7277
cssModuleImports: new Map(),
73-
events: null,
78+
events: buildEvents(),
7479
fs: null,
7580
hasSuccessfulBuild: false,
7681
isActivelyBuilding: false,
@@ -108,7 +113,7 @@ export function mockCompilerCtx(config?: Config) {
108113
return compilerCtx;
109114
}
110115

111-
export function mockBuildCtx(config?: Config, compilerCtx?: CompilerCtx) {
116+
export function mockBuildCtx(config?: Config, compilerCtx?: CompilerCtx): BuildCtx {
112117
if (!config) {
113118
config = mockConfig();
114119
}
@@ -150,3 +155,49 @@ export function mockWindow(html: string = null) {
150155
const win = new MockWindow(html);
151156
return win as any as Window;
152157
}
158+
159+
/**
160+
* This gives you a mock Module, an interface which is the internal compiler
161+
* representation of a module. It includes a bunch of information necessary for
162+
* compilation, this mock basically sets sane defaults for all those values.
163+
*
164+
* @param mod is an override module that you can supply to set particular values
165+
* @returns a module object ready to use in tests!
166+
*/
167+
export const mockModule = (mod: Partial<Module> = {}): Module => ({
168+
cmps: [],
169+
coreRuntimeApis: [],
170+
collectionName: '',
171+
dtsFilePath: '',
172+
excludeFromCollection: false,
173+
externalImports: [],
174+
htmlAttrNames: [],
175+
htmlTagNames: [],
176+
htmlParts: [],
177+
isCollectionDependency: false,
178+
isLegacy: false,
179+
jsFilePath: '',
180+
localImports: [],
181+
originalImports: [],
182+
originalCollectionComponentPath: '',
183+
potentialCmpRefs: [],
184+
sourceFilePath: '',
185+
staticSourceFile: '',
186+
staticSourceFileText: '',
187+
sourceMapPath: '',
188+
sourceMapFileText: '',
189+
190+
// build features
191+
hasVdomAttribute: false,
192+
hasVdomClass: false,
193+
hasVdomFunctional: false,
194+
hasVdomKey: false,
195+
hasVdomListener: false,
196+
hasVdomPropOrAttr: false,
197+
hasVdomRef: false,
198+
hasVdomRender: false,
199+
hasVdomStyle: false,
200+
hasVdomText: false,
201+
hasVdomXlink: false,
202+
...mod,
203+
});

‎src/testing/testing-sys.ts

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export const createTestingSystem = (): TestingSystem => {
6262
sys.writeFile = wrapWrite(sys.writeFile);
6363
sys.writeFileSync = wrapWrite(sys.writeFileSync);
6464

65+
sys.getCompilerExecutingPath = () => 'bin/stencil.js';
66+
6567
Object.defineProperties(sys, {
6668
diskReads: {
6769
get() {

0 commit comments

Comments
 (0)
Please sign in to comment.