Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hasDefaultExport to ModuleInfo #4356

Merged
merged 4 commits into from Jan 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 40 additions & 16 deletions docs/05-plugin-development.md
Expand Up @@ -164,27 +164,42 @@ The `importer` is the fully resolved id of the importing module. When resolving

For those cases, the `isEntry` option will tell you if we are resolving a user defined entry point, an emitted chunk, or if the `isEntry` parameter was provided for the [`this.resolve`](guide/en/#thisresolve) context function.

You can use this for instance as a mechanism to define custom proxy modules for entry points. The following plugin will only expose the default export from entry points while still keeping named exports available for internal usage:
You can use this for instance as a mechanism to define custom proxy modules for entry points. The following plugin will proxy all entry points to inject a polyfill import.

```js
function onlyDefaultForEntriesPlugin() {
function injectPolyfillPlugin() {
return {
name: 'only-default-for-entries',
name: 'inject-polyfill',
async resolveId(source, importer, options) {
if (options.isEntry) {
// We need to skip this plugin to avoid an infinite loop
const resolution = await this.resolve(source, importer, { skipSelf: true, ...options });
// If it cannot be resolved, return `null` so that Rollup displays an error
if (!resolution) return null;
// If it cannot be resolved or is external, just return it so that
// Rollup can display an error
if (!resolution || resolution.external) return resolution;
// In the load hook of the proxy, we want to use this.load to find out
// if the entry has a default export. In the load hook, however, we no
// longer have the full "resolution" object that may contain meta-data
// from other plugins that is only added on first load. Therefore we
// trigger loading here without waiting for it.
this.load(resolution);
return `${resolution.id}?entry-proxy`;
}
return null;
},
load(id) {
async load(id) {
if (id.endsWith('?entry-proxy')) {
const importee = id.slice(0, -'?entry-proxy'.length);
// Note that this will throw if there is no default export
return `export {default} from '${importee}';`;
const entryId = id.slice(0, -'?entry-proxy'.length);
// We need to load and parse the original entry first because we need
// to know if it has a default export
const { hasDefaultExport } = await this.load({ id: entryId });
let code = `import 'polyfill';export * from ${JSON.stringify(entryId)};`;
// Namespace reexports do not reexport default, so we need special
// handling here
if (hasDefaultExport) {
code += `export { default } from ${JSON.stringify(entryId)};`;
}
return code;
}
return null;
}
Expand Down Expand Up @@ -673,6 +688,7 @@ type ModuleInfo = {
id: string; // the id of the module, for convenience
code: string | null; // the source code of the module, `null` if external or not yet available
ast: ESTree.Program; // the parsed abstract syntax tree if available
hasDefaultExport: boolean | null; // is there a default export, `null` if external or not yet available
isEntry: boolean; // is this a user- or plugin-defined entry point
isExternal: boolean; // for external modules that are referenced but not included in the graph
isIncluded: boolean | null; // is the module included after tree-shaking, `null` if external or not yet available
Expand Down Expand Up @@ -718,7 +734,7 @@ This allows you to inspect the final content of modules before deciding how to r
The returned promise will resolve once the module has been fully transformed and parsed but before any imports have been resolved. That means that the resulting `ModuleInfo` will have empty `importedIds`, `dynamicallyImportedIds`, `importedIdResolutions` and `dynamicallyImportedIdResolutions`. This helps to avoid deadlock situations when awaiting `this.load` in a `resolveId` hook. If you are interested in `importedIds` and `dynamicallyImportedIds`, you should implement a `moduleParsed` hook.
Note that with regard to the `moduleSideEffects`, `syntheticNamedExports` and `meta` options, the same restrictions apply as for the `resolveId` hook: Their values only have an effect if the module has not been loaded yet. Thus, it is very important to use `this.resolve` first to find out if any plugins want to set special values for these options in their `resolveId` hook, and pass these options on to `this.load` if appropriate. The example below showcases how this can be handled to add a proxy module for modules containing a special code comment:
Note that with regard to the `moduleSideEffects`, `syntheticNamedExports` and `meta` options, the same restrictions apply as for the `resolveId` hook: Their values only have an effect if the module has not been loaded yet. Thus, it is very important to use `this.resolve` first to find out if any plugins want to set special values for these options in their `resolveId` hook, and pass these options on to `this.load` if appropriate. The example below showcases how this can be handled to add a proxy module for modules containing a special code comment. Note the special handling for re-exporting the default export:
```js
export default function addProxyPlugin() {
Expand All @@ -744,7 +760,16 @@ export default function addProxyPlugin() {
load(id) {
if (id.endsWith('?proxy')) {
const importee = id.slice(0, -'?proxy'.length);
return `console.log('proxy for ${importee}'); export * from ${JSON.stringify(importee)};`;
// Note that namespace reexports do not reexport default exports
let code = `console.log('proxy for ${importee}'); export * from ${JSON.stringify(
importee
)};`;
// We know that while resolving the proxy, importee was already fully
// loaded and parsed, so we can rely on hasDefaultExport
if (this.getModuleInfo(importee).hasDefaultExport) {
code += `export { default } from ${JSON.stringify(importee)};`;
}
return code;
}
return null;
}
Expand Down Expand Up @@ -1142,7 +1167,7 @@ function parentPlugin() {
}
}
// ...plugin hooks
}
};
}

function dependentPlugin() {
Expand All @@ -1151,20 +1176,19 @@ function dependentPlugin() {
name: 'dependent',
buildStart({ plugins }) {
const parentName = 'parent';
const parentPlugin = options.plugins
.find(plugin => plugin.name === parentName);
const parentPlugin = options.plugins.find(plugin => plugin.name === parentName);
if (!parentPlugin) {
// or handle this silently if it is optional
throw new Error(`This plugin depends on the "${parentName}" plugin.`);
}
// now you can access the API methods in subsequent hooks
parentApi = parentPlugin.api;
}
},
transform(code, id) {
if (thereIsAReasonToDoSomething(id)) {
parentApi.doSomething(id);
}
}
}
};
}
```
39 changes: 19 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -64,7 +64,7 @@
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"@rollup/plugin-replace": "^3.0.1",
"@rollup/plugin-typescript": "^8.2.5",
"@rollup/plugin-typescript": "^8.3.0",
"@rollup/pluginutils": "^4.1.2",
"@types/node": "^10.17.60",
"@types/signal-exit": "^3.0.1",
Expand Down Expand Up @@ -100,7 +100,7 @@
"pretty-bytes": "^5.6.0",
"pretty-ms": "^7.0.1",
"requirejs": "^2.3.6",
"rollup": "^2.64.0",
"rollup": "^2.65.0",
"rollup-plugin-license": "^2.6.1",
"rollup-plugin-string": "^3.0.0",
"rollup-plugin-terser": "^7.0.2",
Expand All @@ -111,7 +111,7 @@
"source-map": "^0.7.3",
"source-map-support": "^0.5.21",
"sourcemap-codec": "^1.4.8",
"systemjs": "^6.11.0",
"systemjs": "^6.12.1",
"terser": "^5.10.0",
"tslib": "^2.3.1",
"typescript": "^4.5.5",
Expand Down
1 change: 1 addition & 0 deletions src/ExternalModule.ts
Expand Up @@ -46,6 +46,7 @@ export default class ExternalModule {
get dynamicImporters() {
return dynamicImporters.sort();
},
hasDefaultExport: null,
hasModuleSideEffects,
id,
implicitlyLoadedAfterOneOf: EMPTY_ARRAY,
Expand Down
7 changes: 7 additions & 0 deletions src/Module.ts
Expand Up @@ -277,6 +277,13 @@ export default class Module {
get dynamicImporters() {
return module.dynamicImporters.sort();
},
get hasDefaultExport() {
// This information is only valid after parsing
if (!module.ast) {
return null;
}
return 'default' in module.exports || 'default' in module.reexportDescriptions;
},
hasModuleSideEffects,
id,
get implicitlyLoadedAfterOneOf() {
Expand Down
1 change: 1 addition & 0 deletions src/rollup/types.d.ts
Expand Up @@ -162,6 +162,7 @@ interface ModuleInfo {
dynamicImporters: readonly string[];
dynamicallyImportedIdResolutions: readonly ResolvedId[];
dynamicallyImportedIds: readonly string[];
hasDefaultExport: boolean | null;
hasModuleSideEffects: boolean | 'no-treeshake';
id: string;
implicitlyLoadedAfterOneOf: readonly string[];
Expand Down
Expand Up @@ -73,6 +73,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_MAIN,
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -143,6 +144,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_DEP,
implicitlyLoadedAfterOneOf: [],
Expand Down
Expand Up @@ -69,6 +69,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_MAIN,
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -139,6 +140,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_DEP,
implicitlyLoadedAfterOneOf: [],
Expand Down
Expand Up @@ -117,6 +117,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_MAIN1,
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -236,6 +237,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_MAIN2,
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -354,6 +356,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_DEP,
implicitlyLoadedAfterOneOf: [ID_MAIN1, ID_MAIN2],
Expand Down
Expand Up @@ -68,6 +68,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_MAIN,
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -138,6 +139,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: ID_DEP,
implicitlyLoadedAfterOneOf: [ID_MAIN],
Expand Down
Expand Up @@ -111,6 +111,7 @@ module.exports = {
],
dynamicallyImportedIds: [getId('dynamic')],
dynamicImporters: [],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: getId('main'),
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -145,6 +146,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [getId('dynamic')],
hasDefaultExport: null,
hasModuleSideEffects: true,
id: 'external',
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -177,6 +179,7 @@ module.exports = {
dynamicallyImportedIdResolutions: [],
dynamicallyImportedIds: [],
dynamicImporters: [],
hasDefaultExport: true,
hasModuleSideEffects: true,
id: getId('lib'),
implicitlyLoadedAfterOneOf: [],
Expand Down Expand Up @@ -260,6 +263,7 @@ module.exports = {
],
dynamicallyImportedIds: ['external'],
dynamicImporters: [getId('main')],
hasDefaultExport: false,
hasModuleSideEffects: true,
id: getId('dynamic'),
implicitlyLoadedAfterOneOf: [],
Expand Down