diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa746d878f..1c1c597cf18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # rollup changelog +## 2.66.1 + +_2022-01-25_ + +### Bug Fixes + +- Only warn for conflicting names in namespace reexports if it actually causes problems (#4363) +- Only allow explicit exports or reexports as synthetic namespaces and hide them from namespace reexports (#4364) + +### Pull Requests + +- [#4362](https://github.com/rollup/rollup/pull/4362): refactor: convert exportsByName object to map (@lukastaegert) +- [#4363](https://github.com/rollup/rollup/pull/4363): Do not warn unnecessarily for namespace conflicts (@lukastaegert) +- [#4364](https://github.com/rollup/rollup/pull/4364): Do not expose synthetic namespace export in entries and namespaces (@lukastaegert) + +## 2.66.0 + +_2022-01-22_ + +### Features + +- Note if a module has a default export in ModuleInfo to allow writing better proxy modules (#4356) +- Add option to wait until all imported ids have been resolved when awaiting `this.load` (#4358) + +### Pull Requests + +- [#4356](https://github.com/rollup/rollup/pull/4356): Add hasDefaultExport to ModuleInfo (@lukastaegert) +- [#4358](https://github.com/rollup/rollup/pull/4358): Add "resolveDependencies" option to "this.load" (@lukastaegert) + ## 2.65.0 _2022-01-21_ diff --git a/build-plugins/generate-license-file.ts b/build-plugins/generate-license-file.ts index 42f1e363c38..d1b6cf11640 100644 --- a/build-plugins/generate-license-file.ts +++ b/build-plugins/generate-license-file.ts @@ -1,24 +1,24 @@ import { readFileSync, writeFileSync } from 'fs'; -import { PluginImpl } from 'rollup'; -import license, { Dependency, Person } from 'rollup-plugin-license'; +import type { PluginImpl } from 'rollup'; +import license, { type Dependency, type Person } from 'rollup-plugin-license'; -function generateLicenseFile(dependencies: Dependency[]) { +function generateLicenseFile(dependencies: readonly Dependency[]) { const coreLicense = readFileSync('LICENSE-CORE.md'); - const licenses = new Set(); - const dependencyLicenseTexts = dependencies + const licenses = new Set(); + const dependencyLicenseTexts = Array.from(dependencies) .sort(({ name: nameA }, { name: nameB }) => (nameA! > nameB! ? 1 : -1)) .map(({ name, license, licenseText, author, maintainers, contributors, repository }) => { let text = `## ${name}\n`; if (license) { text += `License: ${license}\n`; } - const names = new Set(); - if (author && author.name) { + const names = new Set(); + if (author?.name) { names.add(author.name); } // TODO there is an inconsistency in the rollup-plugin-license types for (const person of contributors.concat(maintainers as unknown as Person[])) { - if (person && person.name) { + if (person?.name) { names.add(person.name); } } @@ -39,7 +39,7 @@ function generateLicenseFile(dependencies: Dependency[]) { .join('\n') + '\n'; } - licenses.add(license); + licenses.add(license!); return text; }) .join('\n---------------------------------------\n\n'); @@ -59,16 +59,18 @@ function generateLicenseFile(dependencies: Dependency[]) { } } -export default function getLicenseHandler(): { +interface LicenseHandler { collectLicenses: PluginImpl; writeLicense: PluginImpl; -} { - const licenses = new Map(); +} + +export default function getLicenseHandler(): LicenseHandler { + const licenses = new Map(); return { collectLicenses() { - function addLicenses(dependencies: Dependency[]) { + function addLicenses(dependencies: readonly Dependency[]) { for (const dependency of dependencies) { - licenses.set(dependency.name, dependency); + licenses.set(dependency.name!, dependency); } } diff --git a/cli/run/loadConfigFile.ts b/cli/run/loadConfigFile.ts index 18c45e3fe8a..83db5290228 100644 --- a/cli/run/loadConfigFile.ts +++ b/cli/run/loadConfigFile.ts @@ -2,14 +2,14 @@ import { promises as fs } from 'fs'; import { extname, isAbsolute } from 'path'; import { pathToFileURL } from 'url'; import * as rollup from '../../src/node-entry'; -import { MergedRollupOptions } from '../../src/rollup/types'; +import type { MergedRollupOptions } from '../../src/rollup/types'; import { bold } from '../../src/utils/colors'; import { error } from '../../src/utils/error'; import { mergeOptions } from '../../src/utils/options/mergeOptions'; -import { GenericConfigObject } from '../../src/utils/options/options'; +import type { GenericConfigObject } from '../../src/utils/options/options'; import relativeId from '../../src/utils/relativeId'; import { stderr } from '../logging'; -import batchWarnings, { BatchWarnings } from './batchWarnings'; +import batchWarnings, { type BatchWarnings } from './batchWarnings'; import { addCommandPluginsToInputOptions, addPluginsFromCommandOption } from './commandPlugins'; function supportsNativeESM(): boolean { diff --git a/cli/run/loadConfigFromCommand.ts b/cli/run/loadConfigFromCommand.ts index 5f75caf6640..69edc8b83e1 100644 --- a/cli/run/loadConfigFromCommand.ts +++ b/cli/run/loadConfigFromCommand.ts @@ -4,7 +4,7 @@ import batchWarnings, { BatchWarnings } from './batchWarnings'; import { addCommandPluginsToInputOptions } from './commandPlugins'; import { stdinName } from './stdin'; -export default async function loadConfigFromCommand(command: Record): Promise<{ +export default async function loadConfigFromCommand(command: Record): Promise<{ options: MergedRollupOptions[]; warnings: BatchWarnings; }> { diff --git a/cli/run/resetScreen.ts b/cli/run/resetScreen.ts index 3cf34ee8657..6045fce0b44 100644 --- a/cli/run/resetScreen.ts +++ b/cli/run/resetScreen.ts @@ -1,4 +1,4 @@ -import { MergedRollupOptions } from '../../src/rollup/types'; +import type { MergedRollupOptions } from '../../src/rollup/types'; import { stderr } from '../logging'; const CLEAR_SCREEN = '\u001Bc'; diff --git a/cli/run/stdin.ts b/cli/run/stdin.ts index c4c0b853435..ec181af0975 100644 --- a/cli/run/stdin.ts +++ b/cli/run/stdin.ts @@ -1,4 +1,4 @@ -import { Plugin } from '../../src/rollup/types'; +import type { Plugin } from '../../src/rollup/types'; export const stdinName = '-'; diff --git a/cli/run/waitForInput.ts b/cli/run/waitForInput.ts index 946ee79423d..69fdd9254c2 100644 --- a/cli/run/waitForInput.ts +++ b/cli/run/waitForInput.ts @@ -1,5 +1,5 @@ -import { PluginContext } from 'rollup'; -import { NormalizedInputOptions, Plugin } from '../../src/rollup/types'; +import type { PluginContext } from 'rollup'; +import type { NormalizedInputOptions, Plugin } from '../../src/rollup/types'; import { bold } from '../../src/utils/colors'; import { stderr } from '../logging'; @@ -8,9 +8,9 @@ export function waitForInputPlugin(): Plugin { async buildStart(this: PluginContext, options: NormalizedInputOptions) { const inputSpecifiers = Array.isArray(options.input) ? options.input - : Object.keys(options.input as { [entryAlias: string]: string }); + : Object.keys(options.input); - let lastAwaitedSpecifier = null; + let lastAwaitedSpecifier: string | null = null; checkSpecifiers: while (true) { for (const specifier of inputSpecifiers) { if ((await this.resolve(specifier)) === null) { diff --git a/cli/run/watch-cli.ts b/cli/run/watch-cli.ts index ee8e0f3d61b..3783dc20602 100644 --- a/cli/run/watch-cli.ts +++ b/cli/run/watch-cli.ts @@ -1,14 +1,15 @@ import { type FSWatcher, promises } from 'fs'; +import process from 'process'; import chokidar from 'chokidar'; import dateTime from 'date-time'; import ms from 'pretty-ms'; import onExit from 'signal-exit'; import * as rollup from '../../src/node-entry'; -import { MergedRollupOptions, RollupWatcher } from '../../src/rollup/types'; +import type { MergedRollupOptions, RollupWatcher } from '../../src/rollup/types'; import { bold, cyan, green, underline } from '../../src/utils/colors'; import relativeId from '../../src/utils/relativeId'; import { handleError, stderr } from '../logging'; -import { BatchWarnings } from './batchWarnings'; +import type { BatchWarnings } from './batchWarnings'; import { getConfigPath } from './getConfigPath'; import loadAndParseConfigFile from './loadConfigFile'; import loadConfigFromCommand from './loadConfigFromCommand'; diff --git a/docs/05-plugin-development.md b/docs/05-plugin-development.md index 068abb62045..2f02507230e 100644 --- a/docs/05-plugin-development.md +++ b/docs/05-plugin-development.md @@ -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; } @@ -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 @@ -710,15 +726,15 @@ Get ids of the files which has been watched previously. Include both files added #### `this.load` -**Type:** `({id: string, moduleSideEffects?: boolean | 'no-treeshake' | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null}) => Promise` +**Type:** `({id: string, moduleSideEffects?: boolean | 'no-treeshake' | null, syntheticNamedExports?: boolean | string | null, meta?: {[plugin: string]: any} | null, resolveDependencies?: boolean}) => Promise` Loads and parses the module corresponding to the given id, attaching additional meta information to the module if provided. This will trigger the same [`load`](guide/en/#load), [`transform`](guide/en/#transform) and [`moduleParsed`](guide/en/#moduleparsed) hooks that would be triggered if the module were imported by another module. This allows you to inspect the final content of modules before deciding how to resolve them in the [`resolveId`](guide/en/#resolveid) hook and e.g. resolve to a proxy module instead. If the module becomes part of the graph later, there is no additional overhead from using this context function as the module will not be parsed again. The signature allows you to directly pass the return value of [`this.resolve`](guide/en/#thisresolve) to this function as long as it is neither `null` nor external. -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. +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 can either implement a `moduleParsed` hook or pass the `resolveDependencies` flag, which will make the promise returned by `this.load` wait until all dependency ids have been resolved. -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() { @@ -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; } @@ -752,10 +777,77 @@ export default function addProxyPlugin() { } ``` -If the module was already loaded, this will just wait for the parsing to complete and then return its module information. If the module was not yet imported by another module, this will not automatically trigger loading other modules imported by this module. Instead, static and dynamic dependencies will only be loaded once this module has actually been imported at least once. +If the module was already loaded, `this.load` will just wait for the parsing to complete and then return its module information. If the module was not yet imported by another module, it will not automatically trigger loading other modules imported by this module. Instead, static and dynamic dependencies will only be loaded once this module has actually been imported at least once. While it is safe to use `this.load` in a `resolveId` hook, you should be very careful when awaiting it in a `load` or `transform` hook. If there are cyclic dependencies in the module graph, this can easily lead to a deadlock, so any plugin needs to manually take care to avoid waiting for `this.load` inside the `load` or `transform` of the any module that is in a cycle with the loaded module. +Here is another, more elaborate example where we scan entire dependency sub-graphs via the `resolveDependencies` option and repeated calls to `this.load`. We use a `Set` of handled module ids to handle cyclic dependencies. The goal of the plugin is to add a log to each dynamically imported chunk that just lists all modules in the chunk. While this is just a toy example, the technique could be used to e.g. create a single style tag for all CSS imported in the sub-graph. + +```js +// The leading \0 instructs other plugins not to try to resolve, load or +// transform our proxy modules +const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:'; + +export default function dynamicChunkLogsPlugin() { + return { + name: 'dynamic-chunk-logs', + async resolveDynamicImport(specifier, importer) { + // Ignore non-static targets + if (!(typeof specifier === 'string')) return; + // Get the id and initial meta information of the import target + const resolved = await this.resolve(specifier, importer); + // Ignore external targets. Explicit externals have the "external" + // property while unresolved imports are "null". + if (resolved && !resolved.external) { + // We trigger loading the module without waiting for it here + // because meta information attached by resolveId hooks, that may + // be contained in "resolved" and that plugins like "commonjs" may + // depend upon, is only attached to a module the first time it is + // loaded. + // This ensures that this meta information is not lost when we later + // use "this.load" again in the load hook with just the module id. + this.load(resolved); + return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`; + } + }, + async load(id) { + // Ignore all files except our dynamic import proxies + if (!id.startsWith('\0dynamic-import:')) return null; + const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length); + // To allow loading modules in parallel while keeping complexity low, + // we do not directly await each "this.load" call but put their + // promises into an array where we await them via an async for loop. + const moduleInfoPromises = [this.load({ id: actualId, resolveDependencies: true })]; + // We track each loaded dependency here so that we do not load a file + // twice and also do not get stuck when there are circular + // dependencies. + const dependencies = new Set([actualId]); + // "importedIdResolutions" tracks the objects created by resolveId + // hooks. We are using those instead of "importedIds" so that again, + // important meta information is not lost. + for await (const { importedIdResolutions } of moduleInfoPromises) { + for (const resolved of importedIdResolutions) { + if (!dependencies.has(resolved.id)) { + dependencies.add(resolved.id); + moduleInfoPromises.push(this.load({ ...resolved, resolveDependencies: true })); + } + } + } + // We log all modules in a dynamic chunk when it is loaded. + let code = `console.log([${[...dependencies] + .map(JSON.stringify) + .join(', ')}]); export * from ${JSON.stringify(actualId)};`; + // Namespace reexports do not reexport default exports, which is why + // we reexport it manually if it exists + if (this.getModuleInfo(actualId).hasDefaultExport) { + code += `export { default } from ${JSON.stringify(actualId)};`; + } + return code; + } + }; +} +``` + #### `this.meta` **Type:** `{rollupVersion: string, watchMode: boolean}` @@ -1142,7 +1234,7 @@ function parentPlugin() { } } // ...plugin hooks - } + }; } function dependentPlugin() { @@ -1151,20 +1243,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); } } - } + }; } ``` diff --git a/package-lock.json b/package-lock.json index 20c6852f923..b86953d1fb9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "2.65.0", + "version": "2.66.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1117,9 +1117,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001300", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz", - "integrity": "sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA==", + "version": "1.0.30001301", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001301.tgz", + "integrity": "sha512-csfD/GpHMqgEL3V3uIgosvh+SVIQvCh43SNu9HRbP1lnxkKm1kjDG4f32PP571JplkLjfS+mg2p1gxR7MYrrIA==", "dev": true }, "chalk": { @@ -1370,9 +1370,9 @@ } }, "electron-to-chromium": { - "version": "1.4.49", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.49.tgz", - "integrity": "sha512-k/0t1TRfonHIp8TJKfjBu2cKj8MqYTiEpOhci+q7CVEE5xnCQnx1pTa+V8b/sdhe4S3PR4p4iceEQWhGrKQORQ==", + "version": "1.4.51", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.51.tgz", + "integrity": "sha512-JNEmcYl3mk1tGQmy0EvL5eik/CKSBuzAyGP0QFdG6LIgxQe3II0BL1m2zKc2MZMf3uGqHWE1TFddJML0RpjSHQ==", "dev": true }, "emoji-regex": { @@ -1927,6 +1927,34 @@ "fs-extra": "^8.1.0", "matcher-collection": "^2.0.1", "walk-sync": "^2.0.2" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } } }, "flat": { @@ -1968,14 +1996,14 @@ "dev": true }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" } }, "fs.realpath": { @@ -2676,12 +2704,13 @@ } }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" } }, "levn": { @@ -3247,9 +3276,9 @@ "dev": true }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "dev": true }, "natural-compare": { @@ -3931,9 +3960,9 @@ "dev": true }, "resolve": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz", - "integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.1.tgz", + "integrity": "sha512-lfEImVbnolPuaSZuLQ52cAxPBHeI77sPwCOWRdy12UG/CNa8an7oBHH1R+Fp1/mUqSJi4c8TIP6FOIPSZAUrEQ==", "dev": true, "requires": { "is-core-module": "^2.8.0", @@ -3979,9 +4008,9 @@ } }, "rollup": { - "version": "2.64.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.64.0.tgz", - "integrity": "sha512-+c+lbw1lexBKSMb1yxGDVfJ+vchJH3qLbmavR+awDinTDA2C5Ug9u7lkOzj62SCu0PKUExsW36tpgW7Fmpn3yQ==", + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.65.0.tgz", + "integrity": "sha512-ohZVYrhtVMTqqeqH26sngfMiyGDg6gCUReOsoflXvYpzUkDHp8sVG8F9FQxjs72OfnLWpXP2nNNqQ9I0vkRovA==", "dev": true, "requires": { "fsevents": "~2.3.2" @@ -4083,37 +4112,6 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true }, - "sander": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.6.0.tgz", - "integrity": "sha1-rxYkzX+2362Y6+9WUxn5IAeNqSU=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -4450,9 +4448,9 @@ "dev": true }, "systemjs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.11.0.tgz", - "integrity": "sha512-7YPIY44j+BoY+E6cGBSw0oCU8SNTTIHKZgftcBdwWkDzs/M86Fdlr21FrzAyph7Zo8r3CFGscyFe4rrBtixrBg==", + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.12.1.tgz", + "integrity": "sha512-hqTN6kW+pN6/qro6G9OZ7ceDQOcYno020zBQKpZQLsJhYTDMCMNfXi/Y8duF5iW+4WWZr42ry0MMkcRGpbwG2A==", "dev": true }, "terser": { @@ -4624,9 +4622,9 @@ "dev": true }, "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "uri-js": { diff --git a/package.json b/package.json index a2f9057fd46..1ea2df6e1ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rollup", - "version": "2.65.0", + "version": "2.66.1", "description": "Next-generation ES module bundler", "main": "dist/rollup.js", "module": "dist/es/rollup.js", @@ -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", @@ -87,6 +87,7 @@ "eslint-plugin-prettier": "^4.0.0", "execa": "^5.1.1", "fixturify": "^2.1.1", + "fs-extra": "^10.0.0", "hash.js": "^1.1.7", "husky": "^7.0.4", "is-reference": "^3.0.0", @@ -100,18 +101,17 @@ "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", "rollup-plugin-thatworks": "^1.0.4", - "sander": "^0.6.0", "shx": "^0.3.4", "signal-exit": "^3.0.6", "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", diff --git a/scripts/load-perf-config.js b/scripts/load-perf-config.js index 24cb396e93b..647ee9b720f 100644 --- a/scripts/load-perf-config.js +++ b/scripts/load-perf-config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { accessSync, constants } = require('fs'); const path = require('path'); const rollup = require('../dist/rollup.js'); @@ -6,7 +6,7 @@ exports.targetDir = path.resolve(__dirname, '..', 'perf'); const configFile = path.resolve(exports.targetDir, 'rollup.config.js'); try { - fs.accessSync(configFile, fs.constants.R_OK); + accessSync(configFile, constants.R_OK); } catch (e) { console.error( `No valid "rollup.config.js" in ${exports.targetDir}. Did you "npm run perf:init"?` diff --git a/scripts/perf-init.js b/scripts/perf-init.js index 6cd860f73c1..72e617d7db6 100644 --- a/scripts/perf-init.js +++ b/scripts/perf-init.js @@ -1,9 +1,9 @@ /* eslint-disable no-console */ -const fs = require('fs'); +const { accessSync, constants } = require('fs'); const path = require('path'); const execa = require('execa'); -const sander = require('sander'); +const { removeSync } = require('fs-extra'); const repoWithBranch = process.argv[2]; const TARGET_DIR = path.resolve(__dirname, '..', 'perf'); @@ -17,7 +17,7 @@ if (process.argv.length !== 3 || !VALID_REPO.test(repoWithBranch)) { process.exit(1); } console.error(`Cleaning up '${TARGET_DIR}'...`); -sander.rimrafSync(TARGET_DIR); +removeSync(TARGET_DIR); const [, repo, , branch] = VALID_REPO.exec(repoWithBranch); @@ -43,7 +43,7 @@ async function setupNewRepo(repo, branch) { gitArgs.push(`https://github.com/${repo}.git`, TARGET_DIR); await execWithOutput('git', gitArgs); try { - fs.accessSync(path.resolve(TARGET_DIR, 'rollup.config.js'), fs.constants.R_OK); + accessSync(path.resolve(TARGET_DIR, 'rollup.config.js'), constants.R_OK); } catch (e) { throw new Error('The repository needs to have a file "rollup.config.js" at the top level.'); } diff --git a/scripts/perf.js b/scripts/perf.js index 0ccbeaacd25..be74ae5c56f 100644 --- a/scripts/perf.js +++ b/scripts/perf.js @@ -1,6 +1,6 @@ /* global gc */ -const fs = require('fs'); +const { readFileSync, writeFileSync } = require('fs'); const path = require('path'); const colorette = require('colorette'); const prettyBytes = require('pretty-bytes'); @@ -137,7 +137,7 @@ function clearLines(numberOfLines) { function getExistingTimings() { try { - const timings = JSON.parse(fs.readFileSync(perfFile, 'utf8')); + const timings = JSON.parse(readFileSync(perfFile, 'utf8')); console.info( colorette.bold( `Comparing with ${colorette.cyan(perfFile)}. Delete this file to create a new base line.` @@ -151,7 +151,7 @@ function getExistingTimings() { function persistTimings(timings) { try { - fs.writeFileSync(perfFile, JSON.stringify(timings, null, 2), 'utf8'); + writeFileSync(perfFile, JSON.stringify(timings, null, 2), 'utf8'); console.info( colorette.bold( `Saving performance information to new reference file ${colorette.cyan(perfFile)}.` diff --git a/scripts/update-snapshots.js b/scripts/update-snapshots.js index b7e9ee3223a..79b6e28fde8 100755 --- a/scripts/update-snapshots.js +++ b/scripts/update-snapshots.js @@ -1,7 +1,8 @@ #!/usr/bin/env node +const { readdirSync } = require('fs'); const { resolve, join } = require('path'); -const { readdirSync, copydirSync, copyFileSync, rimrafSync } = require('sander'); +const { copySync, removeSync } = require('fs-extra'); const basePath = resolve(__dirname, '../test'); @@ -14,10 +15,10 @@ for (const dir of formDirsToHandle) { formDirsToHandle.push(...testFiles.map(filename => join(dir, filename))); } else if (testFiles.includes('_actual')) { const expectedPath = join(testPath, '_expected'); - rimrafSync(expectedPath); - copydirSync(join(testPath, '_actual')).to(expectedPath); + removeSync(expectedPath); + copySync(join(testPath, '_actual'), expectedPath); } else if (testFiles.includes('_actual.js')) { - copyFileSync(join(testPath, '_actual.js')).to(join(testPath, '_expected.js')); + copySync(join(testPath, '_actual.js'), join(testPath, '_expected.js')); } else { throw new Error(`Could not find test output in ${testPath}`); } @@ -32,8 +33,8 @@ for (const dir of chunkingDirsToHandle) { chunkingDirsToHandle.push(...testFiles.map(filename => join(dir, filename))); } else if (testFiles.includes('_actual')) { const expectedPath = join(testPath, '_expected'); - rimrafSync(expectedPath); - copydirSync(join(testPath, '_actual')).to(expectedPath); + removeSync(expectedPath); + copySync(join(testPath, '_actual'), expectedPath); } else { throw new Error(`Could not find test output in ${testPath}`); } diff --git a/src/Bundle.ts b/src/Bundle.ts index 6c23f85d34e..8fcac9d9bd3 100644 --- a/src/Bundle.ts +++ b/src/Bundle.ts @@ -1,8 +1,8 @@ import Chunk from './Chunk'; -import ExternalModule from './ExternalModule'; -import Graph from './Graph'; +import type ExternalModule from './ExternalModule'; +import type Graph from './Graph'; import Module from './Module'; -import { +import type { GetManualChunk, NormalizedInputOptions, NormalizedOutputOptions, @@ -13,8 +13,8 @@ import { WarningHandler } from './rollup/types'; import { FILE_PLACEHOLDER } from './utils/FileEmitter'; -import { PluginDriver } from './utils/PluginDriver'; -import { Addons, createAddons } from './utils/addons'; +import type { PluginDriver } from './utils/PluginDriver'; +import { type Addons, createAddons } from './utils/addons'; import { getChunkAssignments } from './utils/chunkAssignment'; import commondir from './utils/commondir'; import { @@ -25,7 +25,7 @@ import { warnDeprecation } from './utils/error'; import { sortByExecutionOrder } from './utils/executionOrder'; -import { GenerateCodeSnippets, getGenerateCodeSnippets } from './utils/generateCodeSnippets'; +import { type GenerateCodeSnippets, getGenerateCodeSnippets } from './utils/generateCodeSnippets'; import { basename, isAbsolute } from './utils/path'; import { timeEnd, timeStart } from './utils/timers'; @@ -104,7 +104,7 @@ export default class Bundle { } private async addManualChunks( - manualChunks: Record + manualChunks: Record ): Promise> { const manualChunkAliasByEntry = new Map(); const chunkEntries = await Promise.all( @@ -305,7 +305,7 @@ function validateOptionsForMultiChunkOutput( ); } -function getIncludedModules(modulesById: Map): Module[] { +function getIncludedModules(modulesById: ReadonlyMap): Module[] { return [...modulesById.values()].filter( (module): module is Module => module instanceof Module && @@ -317,7 +317,7 @@ function addModuleToManualChunk( alias: string, module: Module, manualChunkAliasByEntry: Map -) { +): void { const existingAlias = manualChunkAliasByEntry.get(module); if (typeof existingAlias === 'string' && existingAlias !== alias) { return error(errCannotAssignModuleToChunk(module.id, alias, existingAlias)); diff --git a/src/Chunk.ts b/src/Chunk.ts index 437c7400145..4fd0b855972 100644 --- a/src/Chunk.ts +++ b/src/Chunk.ts @@ -1,17 +1,17 @@ -import MagicString, { Bundle as MagicStringBundle, SourceMap } from 'magic-string'; +import MagicString, { Bundle as MagicStringBundle, type SourceMap } from 'magic-string'; import { relative } from '../browser/path'; import ExternalModule from './ExternalModule'; import Module from './Module'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; import FunctionDeclaration from './ast/nodes/FunctionDeclaration'; -import ChildScope from './ast/scopes/ChildScope'; +import type ChildScope from './ast/scopes/ChildScope'; import ExportDefaultVariable from './ast/variables/ExportDefaultVariable'; import LocalVariable from './ast/variables/LocalVariable'; import NamespaceVariable from './ast/variables/NamespaceVariable'; import SyntheticNamedExportVariable from './ast/variables/SyntheticNamedExportVariable'; -import Variable from './ast/variables/Variable'; +import type Variable from './ast/variables/Variable'; import finalisers from './finalisers/index'; -import { +import type { DecodedSourceMapOrMissing, GetInterop, GlobalsOption, @@ -23,11 +23,11 @@ import { RenderedModule, WarningHandler } from './rollup/types'; -import { PluginDriver } from './utils/PluginDriver'; -import { Addons } from './utils/addons'; +import type { PluginDriver } from './utils/PluginDriver'; +import type { Addons } from './utils/addons'; import { collapseSourcemaps } from './utils/collapseSourcemaps'; import { createHash } from './utils/crypto'; -import { deconflictChunk, DependenciesToBeDeconflicted } from './utils/deconflictChunk'; +import { deconflictChunk, type DependenciesToBeDeconflicted } from './utils/deconflictChunk'; import { errCyclicCrossChunkReexport, errFailedValidation, @@ -38,7 +38,7 @@ import { } from './utils/error'; import { escapeId } from './utils/escapeId'; import { assignExportsToMangledNames, assignExportsToNames } from './utils/exportNames'; -import { GenerateCodeSnippets } from './utils/generateCodeSnippets'; +import type { GenerateCodeSnippets } from './utils/generateCodeSnippets'; import getExportMode from './utils/getExportMode'; import { getId } from './utils/getId'; import getIndentString from './utils/getIndentString'; @@ -54,7 +54,7 @@ import { import { basename, dirname, extname, isAbsolute, normalize, resolve } from './utils/path'; import relativeId, { getAliasName } from './utils/relativeId'; import renderChunk from './utils/renderChunk'; -import { RenderOptions } from './utils/renderHelpers'; +import type { RenderOptions } from './utils/renderHelpers'; import { makeUnique, renderNamePattern } from './utils/renderNamePattern'; import { timeEnd, timeStart } from './utils/timers'; import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; @@ -143,7 +143,7 @@ export default class Chunk { private dynamicName: string | null = null; private readonly exportNamesByVariable = new Map(); private readonly exports = new Set(); - private readonly exportsByName: Record = Object.create(null); + private readonly exportsByName = new Map(); private fileName: string | null = null; private implicitEntryModules: Module[] = []; private readonly implicitlyLoadedBefore = new Set(); @@ -165,13 +165,13 @@ export default class Chunk { private strictFacade = false; private usedModules: Module[] = undefined as never; constructor( - private readonly orderedModules: Module[], + private readonly orderedModules: readonly Module[], private readonly inputOptions: NormalizedInputOptions, private readonly outputOptions: NormalizedOutputOptions, - private readonly unsetOptions: Set, + private readonly unsetOptions: ReadonlySet, private readonly pluginDriver: PluginDriver, - private readonly modulesById: Map, - private readonly chunkByModule: Map, + private readonly modulesById: ReadonlyMap, + private readonly chunkByModule: ReadonlyMap, private readonly facadeChunkByModule: Map, private readonly includedNamespaces: Set, private readonly manualChunkAlias: string | null @@ -209,10 +209,10 @@ export default class Chunk { private static generateFacade( inputOptions: NormalizedInputOptions, outputOptions: NormalizedOutputOptions, - unsetOptions: Set, + unsetOptions: ReadonlySet, pluginDriver: PluginDriver, - modulesById: Map, - chunkByModule: Map, + modulesById: ReadonlyMap, + chunkByModule: ReadonlyMap, facadeChunkByModule: Map, includedNamespaces: Set, facadedModule: Module, @@ -295,7 +295,7 @@ export default class Chunk { for (const [variable, exportNames] of exportNamesByVariable) { this.exportNamesByVariable.set(variable, [...exportNames]); for (const exportName of exportNames) { - this.exportsByName[exportName] = variable; + this.exportsByName.set(exportName, variable); } remainingExports.delete(variable); } @@ -503,15 +503,11 @@ export default class Chunk { } getChunkName(): string { - return ( - this.name || (this.name = this.outputOptions.sanitizeFileName(this.getFallbackChunkName())) - ); + return (this.name ??= this.outputOptions.sanitizeFileName(this.getFallbackChunkName())); } getExportNames(): string[] { - return ( - this.sortedExportNames || (this.sortedExportNames = Object.keys(this.exportsByName).sort()) - ); + return (this.sortedExportNames ??= Array.from(this.exportsByName.keys()).sort()); } getRenderedHash(): string { @@ -533,7 +529,7 @@ export default class Chunk { hash.update( this.getExportNames() .map(exportName => { - const variable = this.exportsByName[exportName]; + const variable = this.exportsByName.get(exportName)!; return `${relativeId((variable.module as Module).id).replace(/\\/g, '/')}:${ variable.name }:${exportName}`; @@ -741,13 +737,13 @@ export default class Chunk { hasExports, id: this.id, indent: this.indentString, - intro: addons.intro!, + intro: addons.intro, isEntryFacade: this.outputOptions.preserveModules || (this.facadeModule !== null && this.facadeModule.info.isEntry), isModuleFacade: this.facadeModule !== null, namedExportsMode: this.exportMode !== 'default', - outro: addons.outro!, + outro: addons.outro, snippets, usesTopLevelAwait, warn: this.inputOptions.onwarn @@ -837,7 +833,7 @@ export default class Chunk { } } - private checkCircularDependencyImport(variable: Variable, importingModule: Module) { + private checkCircularDependencyImport(variable: Variable, importingModule: Module): void { const variableModule = variable.module; if (variableModule instanceof Module) { const exportChunk = this.chunkByModule.get(variableModule); @@ -868,14 +864,12 @@ export default class Chunk { existingNames: Record ): string { const hash = createHash(); - hash.update( - [addons.intro, addons.outro, addons.banner, addons.footer].map(addon => addon || '').join(':') - ); + hash.update([addons.intro, addons.outro, addons.banner, addons.footer].join(':')); hash.update(options.format); const dependenciesForHashing = new Set([this]); for (const current of dependenciesForHashing) { if (current instanceof ExternalModule) { - hash.update(':' + current.renderPath); + hash.update(`:${current.renderPath}`); } else { hash.update(current.getRenderedHash()); hash.update(current.generateId(addons, options, existingNames, false)); @@ -1013,7 +1007,7 @@ export default class Chunk { for (const exportName of this.getExportNames()) { if (exportName[0] === '*') continue; - const variable = this.exportsByName[exportName]; + const variable = this.exportsByName.get(exportName)!; if (!(variable instanceof SyntheticNamedExportVariable)) { const module = variable.module; if (module && this.chunkByModule.get(module as Module) !== this) continue; @@ -1171,7 +1165,7 @@ export default class Chunk { dependency = this.modulesById.get(id) as ExternalModule; imported = exportName = '*'; } else { - const variable = this.exportsByName[exportName]; + const variable = this.exportsByName.get(exportName)!; if (variable instanceof SyntheticNamedExportVariable) continue; const module = variable.module!; if (module instanceof Module) { @@ -1287,7 +1281,7 @@ export default class Chunk { }: NormalizedOutputOptions) { const syntheticExports = new Set(); for (const exportName of this.getExportNames()) { - const exportVariable = this.exportsByName[exportName]; + const exportVariable = this.exportsByName.get(exportName)!; if ( format !== 'es' && format !== 'system' && diff --git a/src/ExternalModule.ts b/src/ExternalModule.ts index efd5420de5f..e83f5409a9d 100644 --- a/src/ExternalModule.ts +++ b/src/ExternalModule.ts @@ -1,5 +1,5 @@ import ExternalVariable from './ast/variables/ExternalVariable'; -import { +import type { CustomPluginOptions, ModuleInfo, NormalizedInputOptions, @@ -46,6 +46,7 @@ export default class ExternalModule { get dynamicImporters() { return dynamicImporters.sort(); }, + hasDefaultExport: null, hasModuleSideEffects, id, implicitlyLoadedAfterOneOf: EMPTY_ARRAY, @@ -63,13 +64,13 @@ export default class ExternalModule { }; } - getVariableForExportName(name: string): ExternalVariable { + getVariableForExportName(name: string): [variable: ExternalVariable] { let declaration = this.declarations[name]; - if (declaration) return declaration; + if (declaration) return [declaration]; this.declarations[name] = declaration = new ExternalVariable(this, name); this.exportedVariables.set(declaration, name); - return declaration; + return [declaration]; } setRenderPath(options: NormalizedOutputOptions, inputBase: string): string { diff --git a/src/Graph.ts b/src/Graph.ts index 38fc7a0d7cc..38afb11c095 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -1,10 +1,10 @@ import * as acorn from 'acorn'; -import ExternalModule from './ExternalModule'; +import type ExternalModule from './ExternalModule'; import Module from './Module'; -import { ModuleLoader, UnresolvedModule } from './ModuleLoader'; +import { ModuleLoader, type UnresolvedModule } from './ModuleLoader'; import GlobalScope from './ast/scopes/GlobalScope'; import { PathTracker } from './ast/utils/PathTracker'; -import { +import type { ModuleInfo, ModuleJSON, NormalizedInputOptions, @@ -243,7 +243,7 @@ export default class Graph { for (const importDescription of Object.values(module.importDescriptions)) { if ( importDescription.name !== '*' && - !importDescription.module.getVariableForExportName(importDescription.name) + !importDescription.module.getVariableForExportName(importDescription.name)[0] ) { module.warn( { diff --git a/src/Module.ts b/src/Module.ts index fe458e638b0..0c9d09f1a63 100644 --- a/src/Module.ts +++ b/src/Module.ts @@ -3,31 +3,31 @@ import * as acorn from 'acorn'; import { locate } from 'locate-character'; import MagicString from 'magic-string'; import ExternalModule from './ExternalModule'; -import Graph from './Graph'; +import type Graph from './Graph'; import { createHasEffectsContext, createInclusionContext } from './ast/ExecutionContext'; import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration'; import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration'; -import ExportNamedDeclaration from './ast/nodes/ExportNamedDeclaration'; -import Identifier from './ast/nodes/Identifier'; -import ImportDeclaration from './ast/nodes/ImportDeclaration'; -import ImportExpression from './ast/nodes/ImportExpression'; +import type ExportNamedDeclaration from './ast/nodes/ExportNamedDeclaration'; +import type Identifier from './ast/nodes/Identifier'; +import type ImportDeclaration from './ast/nodes/ImportDeclaration'; +import type ImportExpression from './ast/nodes/ImportExpression'; import Literal from './ast/nodes/Literal'; -import MetaProperty from './ast/nodes/MetaProperty'; +import type MetaProperty from './ast/nodes/MetaProperty'; import * as NodeType from './ast/nodes/NodeType'; import Program from './ast/nodes/Program'; import TemplateLiteral from './ast/nodes/TemplateLiteral'; import VariableDeclaration from './ast/nodes/VariableDeclaration'; import { nodeConstructors } from './ast/nodes/index'; -import { ExpressionNode, NodeBase } from './ast/nodes/shared/Node'; +import type { ExpressionNode, NodeBase } from './ast/nodes/shared/Node'; import ModuleScope from './ast/scopes/ModuleScope'; -import { PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker'; +import { type PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker'; import ExportDefaultVariable from './ast/variables/ExportDefaultVariable'; import ExportShimVariable from './ast/variables/ExportShimVariable'; import ExternalVariable from './ast/variables/ExternalVariable'; import NamespaceVariable from './ast/variables/NamespaceVariable'; import SyntheticNamedExportVariable from './ast/variables/SyntheticNamedExportVariable'; -import Variable from './ast/variables/Variable'; -import { +import type Variable from './ast/variables/Variable'; +import type { CustomPluginOptions, DecodedSourceMapOrMissing, EmittedFile, @@ -62,7 +62,7 @@ import { getOriginalLocation } from './utils/getOriginalLocation'; import { makeLegal } from './utils/identifierHelpers'; import { basename, extname } from './utils/path'; import relativeId from './utils/relativeId'; -import { RenderOptions } from './utils/renderHelpers'; +import type { RenderOptions } from './utils/renderHelpers'; import { timeEnd, timeStart } from './utils/timers'; import { markModuleAndImpureDependenciesAsExecuted } from './utils/traverseStaticDependencies'; import { MISSING_EXPORT_SHIM_VARIABLE } from './utils/variableNames'; @@ -134,13 +134,12 @@ function getVariableForExportNameRecursive( name: string, importerForSideEffects: Module | undefined, isExportAllSearch: boolean, - searchedNamesAndModules = new Map>(), - skipExternalNamespaceReexports: boolean | undefined -): Variable | null { + searchedNamesAndModules = new Map>() +): [variable: Variable | null, indirectExternal?: boolean] { const searchedModules = searchedNamesAndModules.get(name); if (searchedModules) { if (searchedModules.has(target)) { - return isExportAllSearch ? null : error(errCircularReexport(name, target.id)); + return isExportAllSearch ? [null] : error(errCircularReexport(name, target.id)); } searchedModules.add(target); } else { @@ -149,8 +148,7 @@ function getVariableForExportNameRecursive( return target.getVariableForExportName(name, { importerForSideEffects, isExportAllSearch, - searchedNamesAndModules, - skipExternalNamespaceReexports + searchedNamesAndModules }); } @@ -200,7 +198,6 @@ export default class Module { execIndex = Infinity; readonly exportAllSources = new Set(); readonly exports: { [name: string]: ExportDescription } = Object.create(null); - readonly exportsAll: { [name: string]: string } = Object.create(null); readonly implicitlyLoadedAfter = new Set(); readonly implicitlyLoadedBefore = new Set(); readonly importDescriptions: { [name: string]: ImportDescription } = Object.create(null); @@ -235,7 +232,10 @@ export default class Module { private exportNamesByVariable: Map | null = null; private readonly exportShimVariable: ExportShimVariable = new ExportShimVariable(this); private declare magicString: MagicString; - private namespaceReexportsByName: Record = Object.create(null); + private namespaceReexportsByName: Record< + string, + [variable: Variable | null, indirectExternal?: boolean] + > = Object.create(null); private relevantDependencies: Set | null = null; private readonly syntheticExports = new Map(); private syntheticNamespace: Variable | null | undefined = null; @@ -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() { @@ -344,7 +351,11 @@ export default class Module { if (name !== 'default') allExportNames.add(name); } } - + // We do not count the synthetic namespace as a regular export to hide it + // from entry signatures and namespace objects + if (typeof this.info.syntheticNamedExports === 'string') { + allExportNames.delete(this.info.syntheticNamedExports); + } return allExportNames; } @@ -362,7 +373,7 @@ export default class Module { this.implicitlyLoadedAfter.size > 0 ) { for (const exportName of [...this.getReexports(), ...this.getExports()]) { - const exportedVariable = this.getVariableForExportName(exportName); + const [exportedVariable] = this.getVariableForExportName(exportName); if (exportedVariable) { dependencyVariables.add(exportedVariable); } @@ -405,8 +416,7 @@ export default class Module { } const exportNamesByVariable = new Map(); for (const exportName of this.getAllExportNames()) { - if (exportName === this.info.syntheticNamedExports) continue; - let tracedVariable = this.getVariableForExportName(exportName); + let [tracedVariable] = this.getVariableForExportName(exportName); if (tracedVariable instanceof ExportDefaultVariable) { tracedVariable = tracedVariable.getOriginalVariable(); } @@ -458,7 +468,7 @@ export default class Module { const renderedExports: string[] = []; const removedExports: string[] = []; for (const exportName in this.exports) { - const variable = this.getVariableForExportName(exportName); + const [variable] = this.getVariableForExportName(exportName); (variable && variable.included ? renderedExports : removedExports).push(exportName); } return { removedExports, renderedExports }; @@ -467,10 +477,11 @@ export default class Module { getSyntheticNamespace(): Variable { if (this.syntheticNamespace === null) { this.syntheticNamespace = undefined; - this.syntheticNamespace = this.getVariableForExportName( + [this.syntheticNamespace] = this.getVariableForExportName( typeof this.info.syntheticNamedExports === 'string' ? this.info.syntheticNamedExports - : 'default' + : 'default', + { onlyExplicit: true } ); } if (!this.syntheticNamespace) { @@ -486,19 +497,19 @@ export default class Module { { importerForSideEffects, isExportAllSearch, - searchedNamesAndModules, - skipExternalNamespaceReexports + onlyExplicit, + searchedNamesAndModules }: { importerForSideEffects?: Module; isExportAllSearch?: boolean; + onlyExplicit?: boolean; searchedNamesAndModules?: Map>; - skipExternalNamespaceReexports?: boolean; } = EMPTY_OBJECT - ): Variable | null { + ): [variable: Variable | null, indirectExternal?: boolean] { if (name[0] === '*') { if (name.length === 1) { // export * from './other' - return this.namespace; + return [this.namespace]; } else { // export * from 'external' const module = this.graph.modulesById.get(name.slice(1)) as ExternalModule; @@ -509,15 +520,13 @@ export default class Module { // export { foo } from './other' const reexportDeclaration = this.reexportDescriptions[name]; if (reexportDeclaration) { - const variable = getVariableForExportNameRecursive( + const [variable] = getVariableForExportNameRecursive( reexportDeclaration.module, reexportDeclaration.localName, importerForSideEffects, false, - searchedNamesAndModules, - false + searchedNamesAndModules ); - if (!variable) { return this.error( errMissingExport(reexportDeclaration.localName, this.id, reexportDeclaration.module.id), @@ -527,13 +536,13 @@ export default class Module { if (importerForSideEffects) { setAlternativeExporterIfCyclic(variable, importerForSideEffects, this); } - return variable; + return [variable]; } const exportDeclaration = this.exports[name]; if (exportDeclaration) { if (exportDeclaration === MISSING_EXPORT_SHIM_DESCRIPTION) { - return this.exportShimVariable; + return [this.exportShimVariable]; } const name = exportDeclaration.localName; const variable = this.traceVariable(name, importerForSideEffects)!; @@ -545,7 +554,11 @@ export default class Module { ).add(this); setAlternativeExporterIfCyclic(variable, importerForSideEffects, this); } - return variable; + return [variable]; + } + + if (onlyExplicit) { + return [null]; } if (name !== 'default') { @@ -555,30 +568,23 @@ export default class Module { : this.getVariableFromNamespaceReexports( name, importerForSideEffects, - searchedNamesAndModules, - skipExternalNamespaceReexports + searchedNamesAndModules ); - if (!skipExternalNamespaceReexports) { - this.namespaceReexportsByName[name] = foundNamespaceReexport; - } - if (foundNamespaceReexport) { + this.namespaceReexportsByName[name] = foundNamespaceReexport; + if (foundNamespaceReexport[0]) { return foundNamespaceReexport; } } if (this.info.syntheticNamedExports) { - let syntheticExport = this.syntheticExports.get(name); - if (!syntheticExport) { - const syntheticNamespace = this.getSyntheticNamespace(); - syntheticExport = new SyntheticNamedExportVariable( - this.astContext, + return [ + getOrCreate( + this.syntheticExports, name, - syntheticNamespace - ); - this.syntheticExports.set(name, syntheticExport); - return syntheticExport; - } - return syntheticExport; + () => + new SyntheticNamedExportVariable(this.astContext, name, this.getSyntheticNamespace()) + ) + ]; } // we don't want to create shims when we are just @@ -586,10 +592,10 @@ export default class Module { if (!isExportAllSearch) { if (this.options.shimMissingExports) { this.shimMissingExport(name); - return this.exportShimVariable; + return [this.exportShimVariable]; } } - return null; + return [null]; } hasEffects(): boolean { @@ -612,7 +618,7 @@ export default class Module { for (const exportName of this.getExports()) { if (includeNamespaceMembers || exportName !== this.info.syntheticNamedExports) { - const variable = this.getVariableForExportName(exportName)!; + const variable = this.getVariableForExportName(exportName)[0]!; variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { this.includeVariable(variable); @@ -621,7 +627,7 @@ export default class Module { } for (const name of this.getReexports()) { - const variable = this.getVariableForExportName(name); + const [variable] = this.getVariableForExportName(name); if (variable) { variable.deoptimizePath(UNKNOWN_PATH); if (!variable.included) { @@ -650,11 +656,6 @@ export default class Module { linkImports(): void { this.addModulesToImportDescriptions(this.importDescriptions); this.addModulesToImportDescriptions(this.reexportDescriptions); - for (const name in this.exports) { - if (name !== 'default' && name !== this.info.syntheticNamedExports) { - this.exportsAll[name] = this.id; - } - } const externalExportAllModules: ExternalModule[] = []; for (const source of this.exportAllSources) { const module = this.graph.modulesById.get(this.resolvedIds[source].id)!; @@ -663,13 +664,6 @@ export default class Module { continue; } this.exportAllModules.push(module); - for (const name in module.exportsAll) { - if (name in this.exportsAll) { - this.options.onwarn(errNamespaceConflict(name, this, module)); - } else { - this.exportsAll[name] = module.exportsAll[name]; - } - } } this.exportAllModules.push(...externalExportAllModules); } @@ -750,7 +744,7 @@ export default class Module { moduleContext: this.context, options: this.options, requestTreeshakingPass: () => (this.graph.needsTreeshakingPass = true), - traceExport: this.getVariableForExportName.bind(this), + traceExport: (name: string) => this.getVariableForExportName(name)[0], traceVariable: this.traceVariable.bind(this), usesTopLevelAwait: false, warn: this.warn.bind(this) @@ -797,7 +791,7 @@ export default class Module { return otherModule.namespace; } - const declaration = otherModule.getVariableForExportName(importDeclaration.name, { + const [declaration] = otherModule.getVariableForExportName(importDeclaration.name, { importerForSideEffects: importerForSideEffects || this }); @@ -997,12 +991,14 @@ export default class Module { private addRelevantSideEffectDependencies( relevantDependencies: Set, - necessaryDependencies: Set, - alwaysCheckedDependencies: Set + necessaryDependencies: ReadonlySet, + alwaysCheckedDependencies: ReadonlySet ): void { const handledDependencies = new Set(); - const addSideEffectDependencies = (possibleDependencies: Set) => { + const addSideEffectDependencies = ( + possibleDependencies: ReadonlySet + ) => { for (const dependency of possibleDependencies) { if (handledDependencies.has(dependency)) { continue; @@ -1030,52 +1026,54 @@ export default class Module { private getVariableFromNamespaceReexports( name: string, importerForSideEffects?: Module, - searchedNamesAndModules?: Map>, - skipExternalNamespaceReexports = false - ): Variable | null { + searchedNamesAndModules?: Map> + ): [variable: Variable | null, indirectExternal?: boolean] { let foundSyntheticDeclaration: SyntheticNamedExportVariable | null = null; - const skipExternalNamespaceValues = [{ searchedNamesAndModules, skipExternalNamespaces: true }]; - if (!skipExternalNamespaceReexports) { - const clonedSearchedNamesAndModules = new Map>(); - for (const [name, modules] of searchedNamesAndModules || []) { - clonedSearchedNamesAndModules.set(name, new Set(modules)); + const foundInternalDeclarations = new Map(); + const foundExternalDeclarations = new Set(); + for (const module of this.exportAllModules) { + // Synthetic namespaces should not hide "regular" exports of the same name + if (module.info.syntheticNamedExports === name) { + continue; } - skipExternalNamespaceValues.push({ - searchedNamesAndModules: clonedSearchedNamesAndModules, - skipExternalNamespaces: false - }); - } - for (const { skipExternalNamespaces, searchedNamesAndModules } of skipExternalNamespaceValues) { - const foundDeclarations = new Set(); - for (const module of this.exportAllModules) { - if (module instanceof Module || !skipExternalNamespaces) { - const declaration = getVariableForExportNameRecursive( - module, - name, - importerForSideEffects, - true, - searchedNamesAndModules, - skipExternalNamespaces - ); - - if (declaration) { - if (!(declaration instanceof SyntheticNamedExportVariable)) { - foundDeclarations.add(declaration); - } else if (!foundSyntheticDeclaration) { - foundSyntheticDeclaration = declaration; - } - } + const [variable, indirectExternal] = getVariableForExportNameRecursive( + module, + name, + importerForSideEffects, + true, + searchedNamesAndModules + ); + + if (module instanceof ExternalModule || indirectExternal) { + foundExternalDeclarations.add(variable as ExternalVariable); + } else if (variable instanceof SyntheticNamedExportVariable) { + if (!foundSyntheticDeclaration) { + foundSyntheticDeclaration = variable; } + } else if (variable) { + foundInternalDeclarations.set(variable, module); } - if (foundDeclarations.size === 1) { - return [...foundDeclarations][0]; + } + if (foundInternalDeclarations.size > 0) { + const foundDeclarationList = [...foundInternalDeclarations]; + const usedDeclaration = foundDeclarationList[0][0]; + if (foundDeclarationList.length === 1) { + return [usedDeclaration]; } - if (foundDeclarations.size > 1) { - if (skipExternalNamespaces) { - return null; - } - const foundDeclarationList = [...(foundDeclarations as Set)]; - const usedDeclaration = foundDeclarationList[0]; + this.options.onwarn( + errNamespaceConflict( + name, + this.id, + foundDeclarationList.map(([, module]) => module.id) + ) + ); + // TODO we are pretending it was not found while it should behave like "undefined" + return [null]; + } + if (foundExternalDeclarations.size > 0) { + const foundDeclarationList = [...foundExternalDeclarations]; + const usedDeclaration = foundDeclarationList[0]; + if (foundDeclarationList.length > 1) { this.options.onwarn( errAmbiguousExternalNamespaces( name, @@ -1084,13 +1082,13 @@ export default class Module { foundDeclarationList.map(declaration => declaration.module.id) ) ); - return usedDeclaration; } + return [usedDeclaration, true]; } if (foundSyntheticDeclaration) { - return foundSyntheticDeclaration; + return [foundSyntheticDeclaration]; } - return null; + return [null]; } private includeAndGetAdditionalMergedNamespaces(): Variable[] { @@ -1098,7 +1096,7 @@ export default class Module { const syntheticNamespaces = new Set(); for (const module of [this, ...this.exportAllModules]) { if (module instanceof ExternalModule) { - const externalVariable = module.getVariableForExportName('*'); + const [externalVariable] = module.getVariableForExportName('*'); externalVariable.include(); this.imports.add(externalVariable); externalNamespaces.add(externalVariable); diff --git a/src/ModuleLoader.ts b/src/ModuleLoader.ts index 57f27726f1c..98e7fcfaada 100644 --- a/src/ModuleLoader.ts +++ b/src/ModuleLoader.ts @@ -1,7 +1,7 @@ import * as acorn from 'acorn'; import ExternalModule from './ExternalModule'; -import Graph from './Graph'; -import Module, { DynamicImport } from './Module'; +import type Graph from './Graph'; +import Module, { type DynamicImport } from './Module'; import type { CustomPluginOptions, EmittedChunk, @@ -15,7 +15,7 @@ import type { ResolvedId, ResolveIdResult } from './rollup/types'; -import { PluginDriver } from './utils/PluginDriver'; +import type { PluginDriver } from './utils/PluginDriver'; import { EMPTY_OBJECT } from './utils/blank'; import { errBadLoader, @@ -60,6 +60,8 @@ type LoadModulePromise = Promise< loadAndResolveDependencies: Promise ] >; +type PreloadType = boolean | 'resolveDependencies'; +const RESOLVE_DEPENDENCIES: PreloadType = 'resolveDependencies'; export class ModuleLoader { private readonly hasModuleSideEffects: HasModuleSideEffects; @@ -161,12 +163,14 @@ export class ModuleLoader { return module; } - public async preloadModule(resolvedId: NormalizedResolveIdWithoutDefaults): Promise { + public async preloadModule( + resolvedId: { id: string; resolveDependencies?: boolean } & Partial> + ): Promise { const module = await this.fetchModule( - this.addDefaultsToResolvedId(resolvedId)!, + this.getResolvedIdWithDefaults(resolvedId)!, undefined, false, - true + resolvedId.resolveDependencies ? RESOLVE_DEPENDENCIES : true ); return module.info; } @@ -178,7 +182,7 @@ export class ModuleLoader { isEntry: boolean | undefined, skip: readonly { importer: string | undefined; plugin: Plugin; source: string }[] | null = null ): Promise => { - return this.addDefaultsToResolvedId( + return this.getResolvedIdWithDefaults( this.getNormalizedResolvedIdWithoutDefaults( this.options.external(source, importer, false) ? false @@ -199,23 +203,6 @@ export class ModuleLoader { ); }; - private addDefaultsToResolvedId( - resolvedId: NormalizedResolveIdWithoutDefaults | null - ): ResolvedId | null { - if (!resolvedId) { - return null; - } - const external = resolvedId.external || false; - return { - external, - id: resolvedId.id, - meta: resolvedId.meta || {}, - moduleSideEffects: - resolvedId.moduleSideEffects ?? this.hasModuleSideEffects(resolvedId.id, !!external), - syntheticNamedExports: resolvedId.syntheticNamedExports ?? false - }; - } - private addEntryWithImplicitDependants( unresolvedModule: UnresolvedModule, implicitlyLoadedAfter: readonly string[] @@ -354,7 +341,7 @@ export class ModuleLoader { { id, meta, moduleSideEffects, syntheticNamedExports }: ResolvedId, importer: string | undefined, isEntry: boolean, - isPreload: boolean + isPreload: PreloadType ): Promise { const existingModule = this.modulesById.get(id); if (existingModule instanceof Module) { @@ -378,11 +365,9 @@ export class ModuleLoader { this.getResolveDynamicImportPromises(module), loadAndResolveDependenciesPromise ]); - const loadAndResolveDependenciesPromise = loadPromise - .then(([resolveStaticDependencyPromises, resolveDynamicImportPromises]) => - Promise.all([...resolveStaticDependencyPromises, ...resolveDynamicImportPromises]) - ) - .then(() => this.pluginDriver.hookParallel('moduleParsed', [module.info])); + const loadAndResolveDependenciesPromise = waitForDependencyResolution(loadPromise).then(() => + this.pluginDriver.hookParallel('moduleParsed', [module.info]) + ); loadAndResolveDependenciesPromise.catch(() => { /* avoid unhandled promise rejections */ }); @@ -390,6 +375,8 @@ export class ModuleLoader { const resolveDependencyPromises = await loadPromise; if (!isPreload) { await this.fetchModuleDependencies(module, ...resolveDependencyPromises); + } else if (isPreload === RESOLVE_DEPENDENCIES) { + await loadAndResolveDependenciesPromise; } return module; } @@ -546,10 +533,29 @@ export class ModuleLoader { ); } - private async handleExistingModule(module: Module, isEntry: boolean, isPreload: boolean) { + private getResolvedIdWithDefaults( + resolvedId: NormalizedResolveIdWithoutDefaults | null + ): ResolvedId | null { + if (!resolvedId) { + return null; + } + const external = resolvedId.external || false; + return { + external, + id: resolvedId.id, + meta: resolvedId.meta || {}, + moduleSideEffects: + resolvedId.moduleSideEffects ?? this.hasModuleSideEffects(resolvedId.id, !!external), + syntheticNamedExports: resolvedId.syntheticNamedExports ?? false + }; + } + + private async handleExistingModule(module: Module, isEntry: boolean, isPreload: PreloadType) { const loadPromise = this.moduleLoadPromises.get(module)!; if (isPreload) { - return loadPromise; + return isPreload === RESOLVE_DEPENDENCIES + ? waitForDependencyResolution(loadPromise) + : loadPromise; } if (isEntry) { module.info.isEntry = true; @@ -579,10 +585,8 @@ export class ModuleLoader { moduleSideEffects: this.hasModuleSideEffects(source, true), syntheticNamedExports: false }; - } else { - if (resolvedId.external && resolvedId.syntheticNamedExports) { - this.options.onwarn(errExternalSyntheticExports(source, importer)); - } + } else if (resolvedId.external && resolvedId.syntheticNamedExports) { + this.options.onwarn(errExternalSyntheticExports(source, importer)); } return resolvedId; } @@ -621,7 +625,7 @@ export class ModuleLoader { ); } return this.fetchModule( - this.addDefaultsToResolvedId( + this.getResolvedIdWithDefaults( typeof resolveIdResult === 'object' ? (resolveIdResult as NormalizedResolveIdWithoutDefaults) : { id: resolveIdResult } @@ -664,7 +668,7 @@ export class ModuleLoader { )); } return this.handleResolveId( - this.addDefaultsToResolvedId( + this.getResolvedIdWithDefaults( this.getNormalizedResolvedIdWithoutDefaults(resolution, importer, specifier) ), specifier, @@ -709,3 +713,8 @@ function isNotAbsoluteExternal( !isAbsolute(id) ); } + +async function waitForDependencyResolution(loadPromise: LoadModulePromise) { + const [resolveStaticDependencyPromises, resolveDynamicImportPromises] = await loadPromise; + return Promise.all([...resolveStaticDependencyPromises, ...resolveDynamicImportPromises]); +} diff --git a/src/ast/nodes/shared/ArrayPrototype.ts b/src/ast/nodes/shared/ArrayPrototype.ts index a1a1d3d6912..e5ef5d37515 100644 --- a/src/ast/nodes/shared/ArrayPrototype.ts +++ b/src/ast/nodes/shared/ArrayPrototype.ts @@ -133,6 +133,8 @@ export const ARRAY_PROTOTYPE = new ObjectEntity( flat: METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY, flatMap: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY, forEach: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, + groupBy: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, + groupByToMap: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN, includes: METHOD_RETURNS_BOOLEAN, indexOf: METHOD_RETURNS_NUMBER, join: METHOD_RETURNS_STRING, diff --git a/src/ast/scopes/ChildScope.ts b/src/ast/scopes/ChildScope.ts index 96ca9d95c67..cc679dce13c 100644 --- a/src/ast/scopes/ChildScope.ts +++ b/src/ast/scopes/ChildScope.ts @@ -51,8 +51,8 @@ export default class ChildScope extends Scope { addUsedOutsideNames( usedNames: Set, format: InternalModuleFormat, - exportNamesByVariable: ReadonlyMap, - accessedGlobalsByScope: ReadonlyMap> + exportNamesByVariable: ReadonlyMap, + accessedGlobalsByScope: ReadonlyMap> ): void { for (const variable of this.accessedOutsideVariables.values()) { if (variable.included) { @@ -76,8 +76,8 @@ export default class ChildScope extends Scope { deconflict( format: InternalModuleFormat, - exportNamesByVariable: ReadonlyMap, - accessedGlobalsByScope: ReadonlyMap> + exportNamesByVariable: ReadonlyMap, + accessedGlobalsByScope: ReadonlyMap> ): void { const usedNames = new Set(); this.addUsedOutsideNames(usedNames, format, exportNamesByVariable, accessedGlobalsByScope); diff --git a/src/ast/scopes/ModuleScope.ts b/src/ast/scopes/ModuleScope.ts index e6e328a7b89..a264626885c 100644 --- a/src/ast/scopes/ModuleScope.ts +++ b/src/ast/scopes/ModuleScope.ts @@ -33,8 +33,8 @@ export default class ModuleScope extends ChildScope { deconflict( format: InternalModuleFormat, - exportNamesByVariable: ReadonlyMap, - accessedGlobalsByScope: ReadonlyMap> + exportNamesByVariable: ReadonlyMap, + accessedGlobalsByScope: ReadonlyMap> ): void { // all module level variables are already deconflicted when deconflicting the chunk for (const scope of this.children) diff --git a/src/rollup/rollup.ts b/src/rollup/rollup.ts index f1b19802c57..fc9eacf5028 100644 --- a/src/rollup/rollup.ts +++ b/src/rollup/rollup.ts @@ -1,18 +1,18 @@ import { version as rollupVersion } from 'package.json'; import Bundle from '../Bundle'; import Graph from '../Graph'; -import { PluginDriver } from '../utils/PluginDriver'; +import type { PluginDriver } from '../utils/PluginDriver'; import { ensureArray } from '../utils/ensureArray'; import { errAlreadyClosed, errCannotEmitFromOptionsHook, error } from '../utils/error'; import { writeFile } from '../utils/fs'; import { normalizeInputOptions } from '../utils/options/normalizeInputOptions'; import { normalizeOutputOptions } from '../utils/options/normalizeOutputOptions'; -import { GenericConfigObject } from '../utils/options/options'; +import type { GenericConfigObject } from '../utils/options/options'; import { basename, dirname, resolve } from '../utils/path'; import { ANONYMOUS_OUTPUT_PLUGIN_PREFIX, ANONYMOUS_PLUGIN_PREFIX } from '../utils/pluginUtils'; import { SOURCEMAPPING_URL } from '../utils/sourceMappingURL'; import { getTimings, initialiseTimers, timeEnd, timeStart } from '../utils/timers'; -import { +import type { NormalizedInputOptions, NormalizedOutputOptions, OutputAsset, @@ -136,7 +136,7 @@ function applyOptionHook(watchMode: boolean) { }; } -function normalizePlugins(plugins: Plugin[], anonymousPrefix: string): void { +function normalizePlugins(plugins: readonly Plugin[], anonymousPrefix: string): void { for (let pluginIndex = 0; pluginIndex < plugins.length; pluginIndex++) { const plugin = plugins[pluginIndex]; if (!plugin.name) { @@ -148,7 +148,7 @@ function normalizePlugins(plugins: Plugin[], anonymousPrefix: string): void { async function handleGenerateWrite( isWrite: boolean, inputOptions: NormalizedInputOptions, - unsetInputOptions: Set, + unsetInputOptions: ReadonlySet, rawOutputOptions: GenericConfigObject, graph: Graph ): Promise { @@ -181,7 +181,7 @@ function getOutputOptionsAndPluginDriver( rawOutputOptions: GenericConfigObject, inputPluginDriver: PluginDriver, inputOptions: NormalizedInputOptions, - unsetInputOptions: Set + unsetInputOptions: ReadonlySet ): { options: NormalizedOutputOptions; outputPluginDriver: PluginDriver; @@ -202,7 +202,7 @@ function getOutputOptionsAndPluginDriver( function getOutputOptions( inputOptions: NormalizedInputOptions, - unsetInputOptions: Set, + unsetInputOptions: ReadonlySet, rawOutputOptions: GenericConfigObject, outputPluginDriver: PluginDriver ): { options: NormalizedOutputOptions; unsetOptions: Set } { diff --git a/src/rollup/types.d.ts b/src/rollup/types.d.ts index cf2ffa4117e..f1b6307eecf 100644 --- a/src/rollup/types.d.ts +++ b/src/rollup/types.d.ts @@ -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[]; @@ -201,7 +202,9 @@ export interface PluginContext extends MinimalPluginContext { getWatchFiles: () => string[]; /** @deprecated Use `this.resolve` instead */ isExternal: IsExternal; - load: (options: { id: string } & Partial>) => Promise; + load: ( + options: { id: string; resolveDependencies?: boolean } & Partial> + ) => Promise; /** @deprecated Use `this.getModuleIds` instead */ moduleIds: IterableIterator; parse: (input: string, options?: any) => AcornNode; diff --git a/src/utils/FileEmitter.ts b/src/utils/FileEmitter.ts index c88c1205881..2845bcafff6 100644 --- a/src/utils/FileEmitter.ts +++ b/src/utils/FileEmitter.ts @@ -1,7 +1,7 @@ -import Chunk from '../Chunk'; -import Graph from '../Graph'; -import Module from '../Module'; -import { +import type Chunk from '../Chunk'; +import type Graph from '../Graph'; +import type Module from '../Module'; +import type { EmittedChunk, FilePlaceholder, NormalizedInputOptions, @@ -141,7 +141,7 @@ function getAssetFileName(file: ConsumedAsset, referenceId: string): string { function getChunkFileName( file: ConsumedChunk, - facadeChunkByModule: Map | null + facadeChunkByModule: ReadonlyMap | null ): string { const fileName = file.fileName || (file.module && facadeChunkByModule?.get(file.module)?.id); if (!fileName) return error(errChunkNotGeneratedForFileName(file.fileName || file.name)); @@ -150,8 +150,8 @@ function getChunkFileName( export class FileEmitter { private bundle: OutputBundleWithPlaceholders | null = null; - private facadeChunkByModule: Map | null = null; - private filesByReferenceId: Map; + private facadeChunkByModule: ReadonlyMap | null = null; + private readonly filesByReferenceId: Map; private outputOptions: NormalizedOutputOptions | null = null; constructor( @@ -231,7 +231,7 @@ export class FileEmitter { public setOutputBundle = ( outputBundle: OutputBundleWithPlaceholders, outputOptions: NormalizedOutputOptions, - facadeChunkByModule: Map + facadeChunkByModule: ReadonlyMap ): void => { this.outputOptions = outputOptions; this.bundle = outputBundle; diff --git a/src/utils/PluginCache.ts b/src/utils/PluginCache.ts index bc86d580bb6..65e3fb90d45 100644 --- a/src/utils/PluginCache.ts +++ b/src/utils/PluginCache.ts @@ -1,4 +1,4 @@ -import { PluginCache, SerializablePluginCache } from '../rollup/types'; +import type { PluginCache, SerializablePluginCache } from '../rollup/types'; import { error } from './error'; import { ANONYMOUS_OUTPUT_PLUGIN_PREFIX, ANONYMOUS_PLUGIN_PREFIX } from './pluginUtils'; diff --git a/src/utils/PluginContext.ts b/src/utils/PluginContext.ts index 5afd1d7dfc1..2994e32c501 100644 --- a/src/utils/PluginContext.ts +++ b/src/utils/PluginContext.ts @@ -1,13 +1,13 @@ import { version as rollupVersion } from 'package.json'; -import Graph from '../Graph'; -import { +import type Graph from '../Graph'; +import type { NormalizedInputOptions, Plugin, PluginCache, PluginContext, SerializablePluginCache } from '../rollup/types'; -import { FileEmitter } from './FileEmitter'; +import type { FileEmitter } from './FileEmitter'; import { createPluginCache, getCacheForUncacheablePlugin, NO_CACHE } from './PluginCache'; import { BLANK } from './blank'; import { BuildPhase } from './buildPhase'; diff --git a/src/utils/PluginDriver.ts b/src/utils/PluginDriver.ts index b96b54880b1..e37bdab5e0b 100644 --- a/src/utils/PluginDriver.ts +++ b/src/utils/PluginDriver.ts @@ -1,7 +1,7 @@ -import Chunk from '../Chunk'; -import Graph from '../Graph'; -import Module from '../Module'; -import { +import type Chunk from '../Chunk'; +import type Graph from '../Graph'; +import type Module from '../Module'; +import type { AddonHookFunction, AsyncPluginHooks, EmitFile, @@ -63,7 +63,7 @@ const inputHooks = Object.keys(inputHookNames); export type ReplaceContext = (context: PluginContext, plugin: Plugin) => PluginContext; -function throwInvalidHookError(hookName: string, pluginName: string) { +function throwInvalidHookError(hookName: string, pluginName: string): never { return error({ code: 'INVALID_PLUGIN_HOOK', message: `Error running plugin hook ${hookName} for ${pluginName}, expected a function hook.` @@ -77,13 +77,13 @@ export class PluginDriver { public readonly setOutputBundle: ( outputBundle: OutputBundleWithPlaceholders, outputOptions: NormalizedOutputOptions, - facadeChunkByModule: Map + facadeChunkByModule: ReadonlyMap ) => void; private readonly fileEmitter: FileEmitter; private readonly pluginCache: Record | undefined; - private readonly pluginContexts = new Map(); - private readonly plugins: Plugin[]; + private readonly pluginContexts: ReadonlyMap; + private readonly plugins: readonly Plugin[]; constructor( private readonly graph: Graph, @@ -105,12 +105,14 @@ export class PluginDriver { this.setOutputBundle = this.fileEmitter.setOutputBundle.bind(this.fileEmitter); this.plugins = userPlugins.concat(basePluginDriver ? basePluginDriver.plugins : []); const existingPluginNames = new Set(); - for (const plugin of this.plugins) { - this.pluginContexts.set( + + this.pluginContexts = new Map( + this.plugins.map(plugin => [ plugin, getPluginContext(plugin, pluginCache, graph, options, this.fileEmitter, existingPluginNames) - ); - } + ]) + ); + if (basePluginDriver) { for (const plugin of userPlugins) { for (const hook of inputHooks) { diff --git a/src/utils/addons.ts b/src/utils/addons.ts index 87621e62079..c9daeb4f12c 100644 --- a/src/utils/addons.ts +++ b/src/utils/addons.ts @@ -1,12 +1,12 @@ -import { NormalizedOutputOptions } from '../rollup/types'; -import { PluginDriver } from './PluginDriver'; +import type { NormalizedOutputOptions } from '../rollup/types'; +import type { PluginDriver } from './PluginDriver'; import { error } from './error'; export interface Addons { - banner?: string; - footer?: string; - intro?: string; - outro?: string; + banner: string; + footer: string; + intro: string; + outro: string; } const concatSep = (out: string, next: string) => (next ? `${out}\n${next}` : out); diff --git a/src/utils/blank.ts b/src/utils/blank.ts index b0715d324b9..a0741f4a212 100644 --- a/src/utils/blank.ts +++ b/src/utils/blank.ts @@ -1,3 +1,3 @@ -export const BLANK: Record = Object.freeze(Object.create(null)); +export const BLANK: Record = Object.freeze(Object.create(null)); export const EMPTY_OBJECT = Object.freeze({}); export const EMPTY_ARRAY = Object.freeze([]); diff --git a/src/utils/chunkAssignment.ts b/src/utils/chunkAssignment.ts index a4fe740d3b2..4a42e3da78a 100644 --- a/src/utils/chunkAssignment.ts +++ b/src/utils/chunkAssignment.ts @@ -6,11 +6,11 @@ type DependentModuleMap = Map>; type ChunkDefinitions = { alias: string | null; modules: Module[] }[]; export function getChunkAssignments( - entryModules: Module[], - manualChunkAliasByEntry: Map + entryModules: readonly Module[], + manualChunkAliasByEntry: ReadonlyMap ): ChunkDefinitions { const chunkDefinitions: ChunkDefinitions = []; - const modulesInManualChunks = new Set(manualChunkAliasByEntry.keys()); + const modulesInManualChunks = new Set(manualChunkAliasByEntry.keys()); const manualChunkModulesByAlias: Record = Object.create(null); for (const [entry, alias] of manualChunkAliasByEntry) { const chunkModules = (manualChunkModulesByAlias[alias] = @@ -29,7 +29,7 @@ export function getChunkAssignments( function assignEntryToStaticDependencies( entry: Module, - dynamicDependentEntryPoints: Set | null + dynamicDependentEntryPoints: ReadonlySet | null ) { const modulesToHandle = new Set([entry]); for (const module of modulesToHandle) { @@ -54,8 +54,8 @@ export function getChunkAssignments( } function areEntryPointsContainedOrDynamicallyDependent( - entryPoints: Set, - containedIn: Set + entryPoints: ReadonlySet, + containedIn: ReadonlySet ): boolean { const entriesToCheck = new Set(entryPoints); for (const entry of entriesToCheck) { @@ -96,7 +96,7 @@ function addStaticDependenciesToManualChunk( entry: Module, manualChunkModules: Module[], modulesInManualChunks: Set -) { +): void { const modulesToHandle = new Set([entry]); for (const module of modulesToHandle) { modulesInManualChunks.add(module); @@ -109,7 +109,7 @@ function addStaticDependenciesToManualChunk( } } -function analyzeModuleGraph(entryModules: Module[]): { +function analyzeModuleGraph(entryModules: readonly Module[]): { dependentEntryPointsByModule: DependentModuleMap; dynamicEntryModules: Set; } { @@ -117,7 +117,7 @@ function analyzeModuleGraph(entryModules: Module[]): { const dependentEntryPointsByModule: DependentModuleMap = new Map(); const entriesToHandle = new Set(entryModules); for (const currentEntry of entriesToHandle) { - const modulesToHandle = new Set([currentEntry]); + const modulesToHandle = new Set([currentEntry]); for (const module of modulesToHandle) { getOrCreate(dependentEntryPointsByModule, module, () => new Set()).add(currentEntry); for (const dependency of module.getDependenciesToBeIncluded()) { @@ -142,7 +142,7 @@ function analyzeModuleGraph(entryModules: Module[]): { function getDynamicDependentEntryPoints( dependentEntryPointsByModule: DependentModuleMap, - dynamicEntryModules: Set + dynamicEntryModules: ReadonlySet ): DependentModuleMap { const dynamicallyDependentEntryPointsByDynamicEntry: DependentModuleMap = new Map(); for (const dynamicEntry of dynamicEntryModules) { @@ -164,7 +164,7 @@ function getDynamicDependentEntryPoints( } function createChunks( - allEntryPoints: Module[], + allEntryPoints: readonly Module[], assignedEntryPointsByModule: DependentModuleMap ): ChunkDefinitions { const chunkModules: { [chunkSignature: string]: Module[] } = Object.create(null); diff --git a/src/utils/collapseSourcemaps.ts b/src/utils/collapseSourcemaps.ts index 2d188baaa03..2f9ac46025d 100644 --- a/src/utils/collapseSourcemaps.ts +++ b/src/utils/collapseSourcemaps.ts @@ -1,6 +1,6 @@ -import { DecodedSourceMap, SourceMap } from 'magic-string'; -import Module from '../Module'; -import { +import { type DecodedSourceMap, SourceMap } from 'magic-string'; +import type Module from '../Module'; +import type { DecodedSourceMapOrMissing, ExistingDecodedSourceMap, SourceMapSegment, @@ -32,12 +32,12 @@ interface SourceMapSegmentObject { } class Link { - readonly mappings: SourceMapSegment[][]; - readonly names: string[]; + readonly mappings: readonly SourceMapSegment[][]; + readonly names: readonly string[]; readonly sources: (Source | Link)[]; constructor( - map: { mappings: SourceMapSegment[][]; names: string[] }, + map: { mappings: readonly SourceMapSegment[][]; names: readonly string[] }, sources: (Source | Link)[] ) { this.sources = sources; @@ -218,12 +218,8 @@ export function collapseSourcemaps( ) ); - // DecodedSourceMap (from magic-string) uses a number[] instead of the more - // correct SourceMapSegment tuples. Cast it here to gain type safety. - let source = new Link(map as ExistingDecodedSourceMap, moduleSources); - - source = bundleSourcemapChain.reduce(linkMap, source); - + const link = new Link(map, moduleSources); + const source = bundleSourcemapChain.reduce(linkMap, link); let { sources, sourcesContent, names, mappings } = source.traceMappings(); if (file) { diff --git a/src/utils/decodedSourcemap.ts b/src/utils/decodedSourcemap.ts index 3b4acfcf6a4..f49e4f64336 100644 --- a/src/utils/decodedSourcemap.ts +++ b/src/utils/decodedSourcemap.ts @@ -1,5 +1,9 @@ import { decode } from 'sourcemap-codec'; -import { ExistingDecodedSourceMap, ExistingRawSourceMap, SourceMapInput } from '../rollup/types'; +import type { + ExistingDecodedSourceMap, + ExistingRawSourceMap, + SourceMapInput +} from '../rollup/types'; type Input = SourceMapInput | ExistingDecodedSourceMap | undefined; @@ -18,12 +22,7 @@ export function decodedSourcemap(map: Input): ExistingDecodedSourceMap | null { }; } - let mappings; - if (typeof map.mappings === 'string') { - mappings = decode(map.mappings); - } else { - mappings = map.mappings; - } + const mappings = typeof map.mappings === 'string' ? decode(map.mappings) : map.mappings; return { ...(map as ExistingRawSourceMap | ExistingDecodedSourceMap), mappings }; } diff --git a/src/utils/deconflictChunk.ts b/src/utils/deconflictChunk.ts index d19c41dc2a3..e3bd7e91dbe 100644 --- a/src/utils/deconflictChunk.ts +++ b/src/utils/deconflictChunk.ts @@ -1,11 +1,11 @@ -import Chunk from '../Chunk'; +import type Chunk from '../Chunk'; import ExternalModule from '../ExternalModule'; -import Module from '../Module'; -import ChildScope from '../ast/scopes/ChildScope'; +import type Module from '../Module'; +import type ChildScope from '../ast/scopes/ChildScope'; import ExportDefaultVariable from '../ast/variables/ExportDefaultVariable'; -import SyntheticNamedExportVariable from '../ast/variables/SyntheticNamedExportVariable'; -import Variable from '../ast/variables/Variable'; -import { GetInterop, InternalModuleFormat } from '../rollup/types'; +import type SyntheticNamedExportVariable from '../ast/variables/SyntheticNamedExportVariable'; +import type Variable from '../ast/variables/Variable'; +import type { GetInterop, InternalModuleFormat } from '../rollup/types'; import { canDefaultBeTakenFromNamespace, defaultInteropHelpersByInteropType, @@ -15,21 +15,21 @@ import { import { getSafeName } from './safeName'; export interface DependenciesToBeDeconflicted { - deconflictedDefault: Set; - deconflictedNamespace: Set; - dependencies: Set; + deconflictedDefault: ReadonlySet; + deconflictedNamespace: ReadonlySet; + dependencies: ReadonlySet; } const DECONFLICT_IMPORTED_VARIABLES_BY_FORMAT: { [format in InternalModuleFormat]: ( usedNames: Set, - imports: Set, + imports: ReadonlySet, dependenciesToBeDeconflicted: DependenciesToBeDeconflicted, interop: GetInterop, preserveModules: boolean, externalLiveBindings: boolean, - chunkByModule: Map, - syntheticExports: Set + chunkByModule: ReadonlyMap, + syntheticExports: ReadonlySet ) => void; } = { amd: deconflictImportsOther, @@ -43,16 +43,16 @@ const DECONFLICT_IMPORTED_VARIABLES_BY_FORMAT: { export function deconflictChunk( modules: readonly Module[], dependenciesToBeDeconflicted: DependenciesToBeDeconflicted, - imports: Set, + imports: ReadonlySet, usedNames: Set, format: InternalModuleFormat, interop: GetInterop, preserveModules: boolean, externalLiveBindings: boolean, - chunkByModule: Map, - syntheticExports: Set, - exportNamesByVariable: ReadonlyMap, - accessedGlobalsByScope: ReadonlyMap>, + chunkByModule: ReadonlyMap, + syntheticExports: ReadonlySet, + exportNamesByVariable: ReadonlyMap, + accessedGlobalsByScope: ReadonlyMap>, includedNamespaces: ReadonlySet ): void { const reversedModules = modules.slice().reverse(); @@ -88,8 +88,8 @@ function deconflictImportsEsmOrSystem( _interop: GetInterop, preserveModules: boolean, _externalLiveBindings: boolean, - chunkByModule: Map, - syntheticExports: Set + chunkByModule: ReadonlyMap, + syntheticExports: ReadonlySet ) { // This is needed for namespace reexports for (const dependency of dependenciesToBeDeconflicted.dependencies) { @@ -128,12 +128,12 @@ function deconflictImportsEsmOrSystem( function deconflictImportsOther( usedNames: Set, - imports: Set, + imports: ReadonlySet, { deconflictedDefault, deconflictedNamespace, dependencies }: DependenciesToBeDeconflicted, interop: GetInterop, preserveModules: boolean, externalLiveBindings: boolean, - chunkByModule: Map + chunkByModule: ReadonlyMap ): void { for (const chunkOrExternalModule of dependencies) { chunkOrExternalModule.variableName = getSafeName( diff --git a/src/utils/error.ts b/src/utils/error.ts index 3cf7aa0e8ac..f17ea17f0ab 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,6 +1,6 @@ import { locate } from 'locate-character'; -import Module from '../Module'; -import { +import type Module from '../Module'; +import type { NormalizedInputOptions, RollupError, RollupLogProps, @@ -220,7 +220,7 @@ export function errInvalidExportOptionValue(optionValue: string): RollupLogProps export function errIncompatibleExportOptionValue( optionValue: string, - keys: string[], + keys: readonly string[], entryModule: string ): RollupLogProps { return { @@ -340,38 +340,38 @@ export function errMixedExport(facadeModuleId: string, name?: string): RollupLog export function errNamespaceConflict( name: string, - reexportingModule: Module, - additionalExportAllModule: Module + reexportingModuleId: string, + sources: string[] ): RollupWarning { return { code: Errors.NAMESPACE_CONFLICT, message: `Conflicting namespaces: "${relativeId( - reexportingModule.id - )}" re-exports "${name}" from both "${relativeId( - reexportingModule.exportsAll[name] - )}" and "${relativeId(additionalExportAllModule.exportsAll[name])}" (will be ignored)`, + reexportingModuleId + )}" re-exports "${name}" from one of the modules ${printQuotedStringList( + sources.map(moduleId => relativeId(moduleId)) + )} (will be ignored)`, name, - reexporter: reexportingModule.id, - sources: [reexportingModule.exportsAll[name], additionalExportAllModule.exportsAll[name]] + reexporter: reexportingModuleId, + sources }; } export function errAmbiguousExternalNamespaces( name: string, reexportingModule: string, - usedExternalModule: string, - externalModules: string[] + usedModule: string, + sources: string[] ): RollupWarning { return { code: Errors.AMBIGUOUS_EXTERNAL_NAMESPACES, message: `Ambiguous external namespace resolution: "${relativeId( reexportingModule )}" re-exports "${name}" from one of the external modules ${printQuotedStringList( - externalModules.map(module => relativeId(module)) - )}, guessing "${relativeId(usedExternalModule)}".`, + sources.map(module => relativeId(module)) + )}, guessing "${relativeId(usedModule)}".`, name, reexporter: reexportingModule, - sources: externalModules + sources }; } @@ -407,7 +407,7 @@ export function errSyntheticNamedExportsNeedNamespaceExport( syntheticNamedExportsOption )}' needs ${ typeof syntheticNamedExportsOption === 'string' && syntheticNamedExportsOption !== 'default' - ? `an export named "${syntheticNamedExportsOption}"` + ? `an explicit export named "${syntheticNamedExportsOption}"` : 'a default export' } that does not reexport an unresolved named export of the same module.` }; diff --git a/src/utils/escapeId.ts b/src/utils/escapeId.ts index ac94444ad17..ce22df4be1c 100644 --- a/src/utils/escapeId.ts +++ b/src/utils/escapeId.ts @@ -1,6 +1,7 @@ const needsEscapeRegEx = /[\\'\r\n\u2028\u2029]/; const quoteNewlineRegEx = /(['\r\n\u2028\u2029])/g; const backSlashRegEx = /\\/g; + export function escapeId(id: string): string { if (!id.match(needsEscapeRegEx)) return id; return id.replace(backSlashRegEx, '\\\\').replace(quoteNewlineRegEx, '\\$1'); diff --git a/src/utils/executionOrder.ts b/src/utils/executionOrder.ts index d966f993ba7..005313dadc5 100644 --- a/src/utils/executionOrder.ts +++ b/src/utils/executionOrder.ts @@ -1,4 +1,4 @@ -import ExternalModule from '../ExternalModule'; +import type ExternalModule from '../ExternalModule'; import Module from '../Module'; import relativeId from './relativeId'; @@ -71,7 +71,7 @@ export function analyseModuleExecution(entryModules: readonly Module[]): { function getCyclePath( module: Module, parent: Module, - parents: Map + parents: ReadonlyMap ): string[] { const cycleSymbol = Symbol(module.id); const path = [relativeId(module.id)]; diff --git a/src/utils/exportNames.ts b/src/utils/exportNames.ts index dcb297edc63..e24c411555e 100644 --- a/src/utils/exportNames.ts +++ b/src/utils/exportNames.ts @@ -1,16 +1,16 @@ -import Variable from '../ast/variables/Variable'; +import type Variable from '../ast/variables/Variable'; import RESERVED_NAMES from './RESERVED_NAMES'; import { toBase64 } from './base64'; export function assignExportsToMangledNames( exports: ReadonlySet, - exportsByName: Record, + exportsByName: Map, exportNamesByVariable: Map ): void { let nameIndex = 0; for (const variable of exports) { let [exportName] = variable.name; - if (exportsByName[exportName]) { + if (exportsByName.has(exportName)) { do { exportName = toBase64(++nameIndex); // skip past leading number identifiers @@ -18,25 +18,25 @@ export function assignExportsToMangledNames( nameIndex += 9 * 64 ** (exportName.length - 1); exportName = toBase64(nameIndex); } - } while (RESERVED_NAMES.has(exportName) || exportsByName[exportName]); + } while (RESERVED_NAMES.has(exportName) || exportsByName.has(exportName)); } - exportsByName[exportName] = variable; + exportsByName.set(exportName, variable); exportNamesByVariable.set(variable, [exportName]); } } export function assignExportsToNames( exports: ReadonlySet, - exportsByName: Record, + exportsByName: Map, exportNamesByVariable: Map ): void { for (const variable of exports) { let nameIndex = 0; let exportName = variable.name; - while (exportsByName[exportName]) { + while (exportsByName.has(exportName)) { exportName = variable.name + '$' + ++nameIndex; } - exportsByName[exportName] = variable; + exportsByName.set(exportName, variable); exportNamesByVariable.set(variable, [exportName]); } } diff --git a/src/utils/generateCodeSnippets.ts b/src/utils/generateCodeSnippets.ts index 4ec00a5ba99..7db90175846 100644 --- a/src/utils/generateCodeSnippets.ts +++ b/src/utils/generateCodeSnippets.ts @@ -1,4 +1,4 @@ -import { NormalizedOutputOptions } from '../rollup/types'; +import type { NormalizedOutputOptions } from '../rollup/types'; import RESERVED_NAMES from './RESERVED_NAMES'; export interface GenerateCodeSnippets { diff --git a/src/utils/getExportMode.ts b/src/utils/getExportMode.ts index d0854ef9959..d7965457c91 100644 --- a/src/utils/getExportMode.ts +++ b/src/utils/getExportMode.ts @@ -1,5 +1,5 @@ -import Chunk from '../Chunk'; -import { NormalizedOutputOptions, WarningHandler } from '../rollup/types'; +import type Chunk from '../Chunk'; +import type { NormalizedOutputOptions, WarningHandler } from '../rollup/types'; import { errIncompatibleExportOptionValue, errMixedExport, diff --git a/src/utils/getIndentString.ts b/src/utils/getIndentString.ts index b7cd960ba44..dc9f4a36157 100644 --- a/src/utils/getIndentString.ts +++ b/src/utils/getIndentString.ts @@ -1,4 +1,4 @@ -import Module from '../Module'; +import type Module from '../Module'; function guessIndentString(code: string): string | null { const lines = code.split('\n'); diff --git a/src/utils/getOriginalLocation.ts b/src/utils/getOriginalLocation.ts index fb615b0a397..f5f8e7544c8 100644 --- a/src/utils/getOriginalLocation.ts +++ b/src/utils/getOriginalLocation.ts @@ -1,4 +1,4 @@ -import { DecodedSourceMapOrMissing, ExistingDecodedSourceMap } from '../rollup/types'; +import type { DecodedSourceMapOrMissing, ExistingDecodedSourceMap } from '../rollup/types'; export function getOriginalLocation( sourcemapChain: readonly DecodedSourceMapOrMissing[], diff --git a/src/utils/getStaticDependencies.ts b/src/utils/getStaticDependencies.ts index 173206e1a78..41d4a72a26a 100644 --- a/src/utils/getStaticDependencies.ts +++ b/src/utils/getStaticDependencies.ts @@ -1,6 +1,6 @@ -import Chunk from '../Chunk'; +import type Chunk from '../Chunk'; import ExternalModule from '../ExternalModule'; -import Module from '../Module'; +import type Module from '../Module'; export function getStaticDependencies( chunk: Chunk, diff --git a/src/utils/hookActions.ts b/src/utils/hookActions.ts index eca7b630545..b94be501aa9 100644 --- a/src/utils/hookActions.ts +++ b/src/utils/hookActions.ts @@ -1,4 +1,4 @@ -const unfulfilledActions: Set<[string, string, Parameters]> = new Set(); +const unfulfilledActions = new Set<[string, string, Parameters]>(); export function addUnresolvedAction(actionTuple: [string, string, Parameters]): void { unfulfilledActions.add(actionTuple); diff --git a/src/utils/interopHelpers.ts b/src/utils/interopHelpers.ts index cbffbf7d3b1..4c4c86e7d91 100644 --- a/src/utils/interopHelpers.ts +++ b/src/utils/interopHelpers.ts @@ -37,8 +37,8 @@ export const canDefaultBeTakenFromNamespace = ( defaultInteropHelpersByInteropType[interopType] === INTEROP_DEFAULT_VARIABLE; export const getHelpersBlock = ( - additionalHelpers: Set | null, - accessedGlobals: Set, + additionalHelpers: ReadonlySet | null, + accessedGlobals: ReadonlySet, indent: string, snippets: GenerateCodeSnippets, liveBindings: boolean, @@ -72,7 +72,7 @@ const HELPER_GENERATORS: { liveBindings: boolean, freeze: boolean, namespaceToStringTag: boolean, - usedHelpers: Set + usedHelpers: ReadonlySet ) => string; } = { [INTEROP_DEFAULT_LEGACY_VARIABLE](_t, snippets, liveBindings) { diff --git a/src/utils/options/mergeOptions.ts b/src/utils/options/mergeOptions.ts index 472090b1847..9a923172ba9 100644 --- a/src/utils/options/mergeOptions.ts +++ b/src/utils/options/mergeOptions.ts @@ -1,4 +1,4 @@ -import { +import type { ExternalOption, InputOptions, MergedRollupOptions, @@ -8,11 +8,11 @@ import { WarningHandlerWithDefault } from '../../rollup/types'; import { ensureArray } from '../ensureArray'; -import { CommandConfigObject } from './normalizeInputOptions'; +import type { CommandConfigObject } from './normalizeInputOptions'; import { defaultOnWarn, generatedCodePresets, - GenericConfigObject, + type GenericConfigObject, objectifyOption, objectifyOptionWithPresets, treeshakePresets, diff --git a/src/utils/options/normalizeInputOptions.ts b/src/utils/options/normalizeInputOptions.ts index de12b6dd33e..9b8805cb2af 100644 --- a/src/utils/options/normalizeInputOptions.ts +++ b/src/utils/options/normalizeInputOptions.ts @@ -1,5 +1,5 @@ import * as acorn from 'acorn'; -import { +import type { HasModuleSideEffects, InputOptions, ModuleSideEffectsOption, @@ -15,7 +15,7 @@ import { resolve } from '../path'; import relativeId from '../relativeId'; import { defaultOnWarn, - GenericConfigObject, + type GenericConfigObject, getOptionWithPreset, treeshakePresets, warnUnknownOptions diff --git a/src/utils/options/normalizeOutputOptions.ts b/src/utils/options/normalizeOutputOptions.ts index 3bae48b27a3..038204ff2db 100644 --- a/src/utils/options/normalizeOutputOptions.ts +++ b/src/utils/options/normalizeOutputOptions.ts @@ -1,4 +1,4 @@ -import { +import type { InternalModuleFormat, InteropType, NormalizedInputOptions, @@ -12,7 +12,7 @@ import { resolve } from '../path'; import { sanitizeFileName as defaultSanitizeFileName } from '../sanitizeFileName'; import { generatedCodePresets, - GenericConfigObject, + type GenericConfigObject, getOptionWithPreset, warnUnknownOptions } from './options'; @@ -20,7 +20,7 @@ import { export function normalizeOutputOptions( config: OutputOptions, inputOptions: NormalizedInputOptions, - unsetInputOptions: Set + unsetInputOptions: ReadonlySet ): { options: NormalizedOutputOptions; unsetOptions: Set } { // These are options that may trigger special warnings or behaviour later // if the user did not select an explicit value @@ -357,7 +357,14 @@ const getIndent = (config: OutputOptions, compact: boolean): NormalizedOutputOpt return configIndent === false ? '' : configIndent ?? true; }; -const ALLOWED_INTEROP_TYPES = new Set(['auto', 'esModule', 'default', 'defaultOnly', true, false]); +const ALLOWED_INTEROP_TYPES: ReadonlySet = new Set([ + 'auto', + 'esModule', + 'default', + 'defaultOnly', + true, + false +]); const getInterop = ( config: OutputOptions, diff --git a/src/utils/options/options.ts b/src/utils/options/options.ts index cfcf75b9306..5f49c2a7f21 100644 --- a/src/utils/options/options.ts +++ b/src/utils/options/options.ts @@ -1,4 +1,4 @@ -import { +import type { InputOptions, NormalizedGeneratedCodeOptions, NormalizedOutputOptions, @@ -38,7 +38,7 @@ export function warnUnknownOptions( } } -type ObjectValue = Base extends Record ? Base : never; +type ObjectValue = Base extends Record ? Base : never; export const treeshakePresets: { [key in NonNullable< diff --git a/src/utils/pluginUtils.ts b/src/utils/pluginUtils.ts index 1bac7a304c5..05cc8ba25b3 100644 --- a/src/utils/pluginUtils.ts +++ b/src/utils/pluginUtils.ts @@ -1,4 +1,4 @@ -import { NormalizedInputOptions, Plugin, RollupError } from '../rollup/types'; +import type { NormalizedInputOptions, Plugin, RollupError } from '../rollup/types'; import { error, Errors, warnDeprecation } from './error'; export const ANONYMOUS_PLUGIN_PREFIX = 'at position '; @@ -24,9 +24,9 @@ export function throwPluginError( return error(err); } -export const deprecatedHooks: { active: boolean; deprecated: string; replacement: string }[] = [ +const deprecatedHooks = [ { active: true, deprecated: 'resolveAssetUrl', replacement: 'resolveFileUrl' } -]; +] as const; export function warnDeprecatedHooks( plugins: readonly Plugin[], diff --git a/src/utils/pureComments.ts b/src/utils/pureComments.ts index 2177df22e4e..dc385a284fa 100644 --- a/src/utils/pureComments.ts +++ b/src/utils/pureComments.ts @@ -1,5 +1,5 @@ import * as acorn from 'acorn'; -import { BaseWalker, base as basicWalker } from 'acorn-walk'; +import { type BaseWalker, base as basicWalker } from 'acorn-walk'; import { BinaryExpression, CallExpression, @@ -12,16 +12,6 @@ import { } from '../ast/nodes/NodeType'; import { SOURCEMAPPING_URL_RE } from './sourceMappingURL'; -// patch up acorn-walk until class-fields are officially supported -basicWalker.PropertyDefinition = function (node: any, st: any, c: any): void { - if (node.computed) { - c(node.key, st, 'Expression'); - } - if (node.value) { - c(node.value, st, 'Expression'); - } -}; - interface CommentState { annotationIndex: number; annotations: acorn.Comment[]; @@ -60,7 +50,7 @@ const neitherWithespaceNorBrackets = /[^\s(]/g; const noWhitespace = /\S/g; function markPureNode(node: NodeWithComments, comment: acorn.Comment, code: string): void { - const annotatedNodes = []; + const annotatedNodes: NodeWithComments[] = []; let invalidAnnotation: boolean | undefined; const codeInBetween = code.slice(comment.end, node.start); if (doesNotMatchOutsideComment(codeInBetween, neitherWithespaceNorBrackets)) { @@ -139,7 +129,7 @@ function doesNotMatchOutsideComment(code: string, forbiddenChars: RegExp): boole const pureCommentRegex = /[@#]__PURE__/; export function addAnnotations( - comments: acorn.Comment[], + comments: readonly acorn.Comment[], esTreeAst: acorn.Node, code: string ): void { diff --git a/src/utils/reassignedExportsMember.ts b/src/utils/reassignedExportsMember.ts index c7ac51146f1..41abd0ad2e3 100644 --- a/src/utils/reassignedExportsMember.ts +++ b/src/utils/reassignedExportsMember.ts @@ -1,8 +1,8 @@ -import Variable from '../ast/variables/Variable'; +import type Variable from '../ast/variables/Variable'; export function isReassignedExportsMember( variable: Variable, - exportNamesByVariable: ReadonlyMap + exportNamesByVariable: ReadonlyMap ): boolean { return ( variable.renderBaseName !== null && exportNamesByVariable.has(variable) && variable.isReassigned diff --git a/src/utils/renderChunk.ts b/src/utils/renderChunk.ts index ac0c63e62a5..8b2467abd61 100644 --- a/src/utils/renderChunk.ts +++ b/src/utils/renderChunk.ts @@ -1,11 +1,11 @@ -import { +import type { DecodedSourceMapOrMissing, NormalizedOutputOptions, Plugin, RenderedChunk, SourceMapInput } from '../rollup/types'; -import { PluginDriver } from './PluginDriver'; +import type { PluginDriver } from './PluginDriver'; import { decodedSourcemap } from './decodedSourcemap'; export default function renderChunk({ diff --git a/src/utils/renderHelpers.ts b/src/utils/renderHelpers.ts index 6e9c6c2d209..fb328ceb582 100644 --- a/src/utils/renderHelpers.ts +++ b/src/utils/renderHelpers.ts @@ -1,9 +1,9 @@ -import MagicString from 'magic-string'; -import { Node, StatementNode } from '../ast/nodes/shared/Node'; -import Variable from '../ast/variables/Variable'; -import { InternalModuleFormat } from '../rollup/types'; -import { PluginDriver } from './PluginDriver'; -import { GenerateCodeSnippets } from './generateCodeSnippets'; +import type MagicString from 'magic-string'; +import type { Node, StatementNode } from '../ast/nodes/shared/Node'; +import type Variable from '../ast/variables/Variable'; +import type { InternalModuleFormat } from '../rollup/types'; +import type { PluginDriver } from './PluginDriver'; +import type { GenerateCodeSnippets } from './generateCodeSnippets'; import { treeshakeNode } from './treeshakeNode'; export interface RenderOptions { diff --git a/src/utils/renderNamePattern.ts b/src/utils/renderNamePattern.ts index aed61cc8a68..0bf89b563b7 100644 --- a/src/utils/renderNamePattern.ts +++ b/src/utils/renderNamePattern.ts @@ -30,7 +30,7 @@ export function renderNamePattern( }); } -export function makeUnique(name: string, existingNames: Record): string { +export function makeUnique(name: string, existingNames: Record): string { const existingNamesLowercase = new Set(Object.keys(existingNames).map(key => key.toLowerCase())); if (!existingNamesLowercase.has(name.toLocaleLowerCase())) return name; diff --git a/src/utils/resolveId.ts b/src/utils/resolveId.ts index 7e21b753aa7..fda87e0c911 100644 --- a/src/utils/resolveId.ts +++ b/src/utils/resolveId.ts @@ -1,5 +1,5 @@ -import { CustomPluginOptions, Plugin, ResolvedId, ResolveIdResult } from '../rollup/types'; -import { PluginDriver } from './PluginDriver'; +import type { CustomPluginOptions, Plugin, ResolvedId, ResolveIdResult } from '../rollup/types'; +import type { PluginDriver } from './PluginDriver'; import { lstat, readdir, realpath } from './fs'; import { basename, dirname, isAbsolute, resolve } from './path'; import { resolveIdViaPlugins } from './resolveIdViaPlugins'; diff --git a/src/utils/resolveIdViaPlugins.ts b/src/utils/resolveIdViaPlugins.ts index 20d177e6bc2..02a4975a8ca 100644 --- a/src/utils/resolveIdViaPlugins.ts +++ b/src/utils/resolveIdViaPlugins.ts @@ -1,11 +1,11 @@ -import { +import type { CustomPluginOptions, Plugin, PluginContext, ResolvedId, ResolveIdResult } from '../rollup/types'; -import { PluginDriver, ReplaceContext } from './PluginDriver'; +import type { PluginDriver, ReplaceContext } from './PluginDriver'; import { BLANK } from './blank'; export function resolveIdViaPlugins( diff --git a/src/utils/systemJsRendering.ts b/src/utils/systemJsRendering.ts index 6d8e9fed0a9..600778e779f 100644 --- a/src/utils/systemJsRendering.ts +++ b/src/utils/systemJsRendering.ts @@ -1,6 +1,6 @@ -import MagicString from 'magic-string'; -import Variable from '../ast/variables/Variable'; -import { RenderOptions } from './renderHelpers'; +import type MagicString from 'magic-string'; +import type Variable from '../ast/variables/Variable'; +import type { RenderOptions } from './renderHelpers'; export function getSystemExportStatement( exportedVariables: readonly Variable[], diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 67cc23a3e20..a4607242320 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -1,6 +1,6 @@ import MagicString, { SourceMap } from 'magic-string'; -import Module from '../Module'; -import { +import type Module from '../Module'; +import type { DecodedSourceMapOrMissing, EmittedFile, ExistingRawSourceMap, @@ -15,7 +15,7 @@ import { WarningHandler } from '../rollup/types'; import { getTrackedPluginCache } from './PluginCache'; -import { PluginDriver } from './PluginDriver'; +import type { PluginDriver } from './PluginDriver'; import { collapseSourcemap } from './collapseSourcemaps'; import { decodedSourcemap } from './decodedSourcemap'; import { augmentCodeLocation, errNoTransformMapOrAstWithoutCode } from './error'; diff --git a/src/utils/traverseStaticDependencies.ts b/src/utils/traverseStaticDependencies.ts index c371d9f78ac..54a3bb42aca 100644 --- a/src/utils/traverseStaticDependencies.ts +++ b/src/utils/traverseStaticDependencies.ts @@ -1,5 +1,5 @@ import ExternalModule from '../ExternalModule'; -import Module from '../Module'; +import type Module from '../Module'; export function markModuleAndImpureDependenciesAsExecuted(baseModule: Module): void { baseModule.isExecuted = true; diff --git a/src/utils/treeshakeNode.ts b/src/utils/treeshakeNode.ts index d59d4ee5dcf..47375d87021 100644 --- a/src/utils/treeshakeNode.ts +++ b/src/utils/treeshakeNode.ts @@ -1,6 +1,6 @@ -import MagicString from 'magic-string'; +import type MagicString from 'magic-string'; import * as NodeType from '../ast/nodes/NodeType'; -import { Node } from '../ast/nodes/shared/Node'; +import type { Node } from '../ast/nodes/shared/Node'; export function treeshakeNode(node: Node, code: MagicString, start: number, end: number): void { code.remove(start, end); diff --git a/src/watch/watch-proxy.ts b/src/watch/watch-proxy.ts index a760d20cdfd..0ca30d39a1b 100644 --- a/src/watch/watch-proxy.ts +++ b/src/watch/watch-proxy.ts @@ -1,8 +1,8 @@ import { EventEmitter } from 'events'; -import { RollupWatcher } from '../rollup/types'; +import type { RollupWatcher } from '../rollup/types'; import { ensureArray } from '../utils/ensureArray'; import { errInvalidOption, error } from '../utils/error'; -import { GenericConfigObject } from '../utils/options/options'; +import type { GenericConfigObject } from '../utils/options/options'; import { loadFsEvents } from './fsevents-importer'; class WatchEmitter extends EventEmitter { diff --git a/src/watch/watch.ts b/src/watch/watch.ts index 134124cc7b1..000183491e4 100644 --- a/src/watch/watch.ts +++ b/src/watch/watch.ts @@ -1,7 +1,7 @@ import { resolve } from 'path'; import { createFilter } from '@rollup/pluginutils'; import { rollupInternal } from '../rollup/rollup'; -import { +import type { ChangeEvent, MergedRollupOptions, OutputOptions, @@ -11,7 +11,7 @@ import { WatcherOptions } from '../rollup/types'; import { mergeOptions } from '../utils/options/mergeOptions'; -import { GenericConfigObject } from '../utils/options/options'; +import type { GenericConfigObject } from '../utils/options/options'; import { FileWatcher } from './fileWatcher'; const eventsRewrites: Record> = { diff --git a/test/chunking-form/samples/deprecated/emit-asset/_config.js b/test/chunking-form/samples/deprecated/emit-asset/_config.js index dad3aeeaf09..f9625ac7974 100644 --- a/test/chunking-form/samples/deprecated/emit-asset/_config.js +++ b/test/chunking-form/samples/deprecated/emit-asset/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -21,7 +21,7 @@ module.exports = { if (id.endsWith('.svg')) { return `export default import.meta.ROLLUP_ASSET_URL_${this.emitAsset( path.basename(id), - fs.readFileSync(id) + readFileSync(id) )};`; } } diff --git a/test/chunking-form/samples/emit-file/reference-files/_config.js b/test/chunking-form/samples/emit-file/reference-files/_config.js index 57ba1d3de55..6569204399f 100644 --- a/test/chunking-form/samples/emit-file/reference-files/_config.js +++ b/test/chunking-form/samples/emit-file/reference-files/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -21,7 +21,7 @@ module.exports = { return `export default import.meta.ROLLUP_FILE_URL_${this.emitFile({ type: 'asset', name: path.basename(id), - source: fs.readFileSync(id) + source: readFileSync(id) })};`; } } diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js index 661bf005fac..0651fc7ad26 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-emitted-entry/_config.js @@ -73,6 +73,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], @@ -143,6 +144,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], diff --git a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js index 7856f608a24..e25d1c64c4d 100644 --- a/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/implicitly-dependent-entry/_config.js @@ -69,6 +69,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], @@ -139,6 +140,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], diff --git a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js index c0d26c83d4b..8802c21bf44 100644 --- a/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/multiple-dependencies/_config.js @@ -117,6 +117,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN1, implicitlyLoadedAfterOneOf: [], @@ -236,6 +237,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN2, implicitlyLoadedAfterOneOf: [], @@ -354,6 +356,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [ID_MAIN1, ID_MAIN2], diff --git a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js index 699ba45512e..14c405191ee 100644 --- a/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js +++ b/test/chunking-form/samples/implicit-dependencies/single-dependency/_config.js @@ -68,6 +68,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], @@ -138,6 +139,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [ID_MAIN], diff --git a/test/cli/index.js b/test/cli/index.js index 267b0b27e2d..15c32331e0e 100644 --- a/test/cli/index.js +++ b/test/cli/index.js @@ -1,7 +1,9 @@ const assert = require('assert'); const { exec } = require('child_process'); -const path = require('path'); -const sander = require('sander'); +const { existsSync, readFileSync } = require('fs'); +const { basename, resolve, sep } = require('path'); +const process = require('process'); +const { copySync, removeSync, statSync } = require('fs-extra'); const { normaliseOutput, runTestSuiteWithSamples, @@ -11,12 +13,12 @@ const { const cwd = process.cwd(); -sander.rimrafSync(__dirname, 'node_modules'); -sander.copydirSync(__dirname, 'node_modules_rename_me').to(__dirname, 'node_modules'); +removeSync(resolve(__dirname, 'node_modules')); +copySync(resolve(__dirname, 'node_modules_rename_me'), resolve(__dirname, 'node_modules')); runTestSuiteWithSamples( 'cli', - path.resolve(__dirname, 'samples'), + resolve(__dirname, 'samples'), (dir, config) => { // allow to repeat flaky tests for debugging on CLI for (let pass = 0; pass < (config.repeat || 1); pass++) { @@ -27,7 +29,7 @@ runTestSuiteWithSamples( ); function runTest(dir, config, pass) { - const name = path.basename(dir) + ': ' + config.description; + const name = basename(dir) + ': ' + config.description; (config.skip ? it.skip : config.solo ? it.only : it)( pass > 0 ? `${name} (pass ${pass + 1})` : name, done => { @@ -37,7 +39,7 @@ function runTest(dir, config, pass) { } const command = config.command.replace( /(^| )rollup($| )/g, - `node ${path.resolve(__dirname, '../../dist/bin')}${path.sep}rollup ` + `node ${resolve(__dirname, '../../dist/bin')}${sep}rollup ` ); Promise.resolve(config.before && config.before()).then(() => { @@ -111,10 +113,7 @@ function runTest(dir, config, pass) { } catch (err) { done(err); } - } else if ( - sander.existsSync('_expected') && - sander.statSync('_expected').isDirectory() - ) { + } else if (existsSync('_expected') && statSync('_expected').isDirectory()) { try { assertDirectoriesAreEqual('_actual', '_expected'); done(); @@ -122,7 +121,7 @@ function runTest(dir, config, pass) { done(err); } } else { - const expected = sander.readFileSync('_expected.js').toString(); + const expected = readFileSync('_expected.js', 'utf8'); try { assert.equal(normaliseOutput(code), normaliseOutput(expected)); done(); diff --git a/test/cli/samples/config-no-output/_config.js b/test/cli/samples/config-no-output/_config.js index 148b688c86b..1f0696157c2 100644 --- a/test/cli/samples/config-no-output/_config.js +++ b/test/cli/samples/config-no-output/_config.js @@ -1,12 +1,12 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync, unlinkSync } = require('fs'); module.exports = { description: 'uses -o from CLI', command: 'rollup -c -o output.js', test() { - const output = fs.readFileSync('output.js', 'utf-8'); + const output = readFileSync('output.js', 'utf-8'); assert.equal(output.trim(), 'console.log(42);'); - fs.unlinkSync('output.js'); + unlinkSync('output.js'); } }; diff --git a/test/cli/samples/sourcemap-hidden/_config.js b/test/cli/samples/sourcemap-hidden/_config.js index 6bd4d61f101..5dd0871d5a0 100644 --- a/test/cli/samples/sourcemap-hidden/_config.js +++ b/test/cli/samples/sourcemap-hidden/_config.js @@ -1,16 +1,16 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync, unlinkSync } = require('fs'); module.exports = { description: 'omits sourcemap comments', command: 'rollup -i main.js -f es -m hidden -o output.js', test() { - assert.equal(fs.readFileSync('output.js', 'utf-8').trim(), 'console.log( 42 );'); - fs.unlinkSync('output.js'); + assert.equal(readFileSync('output.js', 'utf-8').trim(), 'console.log( 42 );'); + unlinkSync('output.js'); assert.equal( - fs.readFileSync('output.js.map', 'utf-8').trim(), + readFileSync('output.js.map', 'utf-8').trim(), '{"version":3,"file":"output.js","sources":["main.js"],"sourcesContent":["console.log( 42 );\\n"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,EAAE,EAAE,EAAE"}' ); - fs.unlinkSync('output.js.map'); + unlinkSync('output.js.map'); } }; diff --git a/test/cli/samples/wait-for-bundle-input-object/_config.js b/test/cli/samples/wait-for-bundle-input-object/_config.js index 4bda6e35d5c..dfc50422854 100644 --- a/test/cli/samples/wait-for-bundle-input-object/_config.js +++ b/test/cli/samples/wait-for-bundle-input-object/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../utils'); @@ -13,8 +13,8 @@ module.exports = { third = path.resolve(__dirname, 'third.js'); }, after() { - fs.unlinkSync(second); - fs.unlinkSync(third); + unlinkSync(second); + unlinkSync(third); }, abortOnStderr(data) { if (data.includes('waiting for input second')) { diff --git a/test/cli/samples/wait-for-bundle-input/_config.js b/test/cli/samples/wait-for-bundle-input/_config.js index e27820f5bb6..26a18263b2b 100644 --- a/test/cli/samples/wait-for-bundle-input/_config.js +++ b/test/cli/samples/wait-for-bundle-input/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../utils'); @@ -11,7 +11,7 @@ module.exports = { mainFile = path.resolve(__dirname, 'main.js'); }, after() { - fs.unlinkSync(mainFile); + unlinkSync(mainFile); }, abortOnStderr(data) { if (data.includes('waiting for input main.js')) { diff --git a/test/cli/samples/warn-broken-sourcemap/_config.js b/test/cli/samples/warn-broken-sourcemap/_config.js index 44597f2d557..7ec82baaf01 100644 --- a/test/cli/samples/warn-broken-sourcemap/_config.js +++ b/test/cli/samples/warn-broken-sourcemap/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync } = require('fs'); const path = require('path'); const { assertIncludes } = require('../../../utils.js'); @@ -6,8 +6,8 @@ module.exports = { description: 'displays warnings for broken sourcemaps', command: 'rollup -c', stderr: stderr => { - fs.unlinkSync(path.resolve(__dirname, 'bundle')); - fs.unlinkSync(path.resolve(__dirname, 'bundle.map')); + unlinkSync(path.resolve(__dirname, 'bundle')); + unlinkSync(path.resolve(__dirname, 'bundle.map')); assertIncludes( stderr, '(!) Broken sourcemap\n' + diff --git a/test/cli/samples/warn-broken-sourcemaps/_config.js b/test/cli/samples/warn-broken-sourcemaps/_config.js index b3e2e2ba768..a6aafd97a38 100644 --- a/test/cli/samples/warn-broken-sourcemaps/_config.js +++ b/test/cli/samples/warn-broken-sourcemaps/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync } = require('fs'); const path = require('path'); const { assertIncludes } = require('../../../utils.js'); @@ -6,8 +6,8 @@ module.exports = { description: 'displays warnings for broken sourcemaps', command: 'rollup -c', stderr: stderr => { - fs.unlinkSync(path.resolve(__dirname, 'bundle')); - fs.unlinkSync(path.resolve(__dirname, 'bundle.map')); + unlinkSync(path.resolve(__dirname, 'bundle')); + unlinkSync(path.resolve(__dirname, 'bundle.map')); assertIncludes( stderr, '(!) Broken sourcemap\n' + diff --git a/test/cli/samples/watch/bundle-error/_config.js b/test/cli/samples/watch/bundle-error/_config.js index 2f38a2e2407..25046a7f5f1 100644 --- a/test/cli/samples/watch/bundle-error/_config.js +++ b/test/cli/samples/watch/bundle-error/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync, writeFileSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../../utils'); @@ -9,11 +9,11 @@ module.exports = { command: 'rollup -cw', before() { mainFile = path.resolve(__dirname, 'main.js'); - fs.writeFileSync(mainFile, '<=>'); + writeFileSync(mainFile, '<=>'); }, after() { // synchronous sometimes does not seem to work, probably because the watch is not yet removed properly - setTimeout(() => fs.unlinkSync(mainFile), 300); + setTimeout(() => unlinkSync(mainFile), 300); }, abortOnStderr(data) { if (data.includes('Error: Unexpected token')) { diff --git a/test/cli/samples/watch/close-stdin/wrapper.js b/test/cli/samples/watch/close-stdin/wrapper.js index a978de79c4b..32506153c5d 100755 --- a/test/cli/samples/watch/close-stdin/wrapper.js +++ b/test/cli/samples/watch/close-stdin/wrapper.js @@ -1,7 +1,7 @@ #!/usr/bin/env node const stream = require('stream'); -const fs = require('fs'); +const { mkdirSync, readFileSync, writeFileSync } = require('fs'); const chokidar = require('chokidar'); const path = require('path'); @@ -14,13 +14,13 @@ process.stdin = new stream.Readable({ }); const outputDir = path.resolve(__dirname, '_actual'); -fs.mkdirSync(outputDir); +mkdirSync(outputDir); const outputFile = path.resolve(outputDir, 'out.js'); const INITIAL_OUTPUT = 'NOT WRITTEN'; -fs.writeFileSync(outputFile, INITIAL_OUTPUT); +writeFileSync(outputFile, INITIAL_OUTPUT); const watcher = chokidar.watch(outputFile).on('change', () => { - if (fs.readFileSync(outputFile, 'utf8') !== INITIAL_OUTPUT) { + if (readFileSync(outputFile, 'utf8') !== INITIAL_OUTPUT) { watcher.close(); // This closes stdin process.stdin.push(null); diff --git a/test/cli/samples/watch/watch-config-early-update/_config.js b/test/cli/samples/watch/watch-config-early-update/_config.js index 031d5d38d14..3af6f00847f 100644 --- a/test/cli/samples/watch/watch-config-early-update/_config.js +++ b/test/cli/samples/watch/watch-config-early-update/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { mkdirSync, unlinkSync } = require('fs'); const path = require('path'); const { writeAndSync, writeAndRetry } = require('../../../../utils'); @@ -15,7 +15,7 @@ module.exports = { // parsed. To do that, the first config hooks into process.stderr and looks for a log from the // second config. // That way, we simulate a complicated config file being changed while it is parsed. - fs.mkdirSync(path.join(__dirname, '_actual')); + mkdirSync(path.join(__dirname, '_actual')); writeAndSync( configFile, ` @@ -44,7 +44,7 @@ module.exports = { ); }, after() { - fs.unlinkSync(configFile); + unlinkSync(configFile); }, abortOnStderr(data) { if (data === 'initial\n') { diff --git a/test/cli/samples/watch/watch-config-error/_config.js b/test/cli/samples/watch/watch-config-error/_config.js index b313c17851f..15ac4f74345 100644 --- a/test/cli/samples/watch/watch-config-error/_config.js +++ b/test/cli/samples/watch/watch-config-error/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync, writeFileSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../../utils'); @@ -9,7 +9,7 @@ module.exports = { command: 'rollup -cw', before() { configFile = path.resolve(__dirname, 'rollup.config.js'); - fs.writeFileSync( + writeFileSync( configFile, ` export default { @@ -22,7 +22,7 @@ module.exports = { ); }, after() { - fs.unlinkSync(configFile); + unlinkSync(configFile); }, abortOnStderr(data) { if (data.includes(`created _actual${path.sep}main1.js`)) { diff --git a/test/cli/samples/watch/watch-config-initial-error/_config.js b/test/cli/samples/watch/watch-config-initial-error/_config.js index d2ecd6532ec..7faa10b357a 100644 --- a/test/cli/samples/watch/watch-config-initial-error/_config.js +++ b/test/cli/samples/watch/watch-config-initial-error/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync, writeFileSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../../utils'); @@ -9,10 +9,10 @@ module.exports = { command: 'rollup -cw', before() { configFile = path.join(__dirname, 'rollup.config.js'); - fs.writeFileSync(configFile, 'throw new Error("Config contains initial errors");'); + writeFileSync(configFile, 'throw new Error("Config contains initial errors");'); }, after() { - fs.unlinkSync(configFile); + unlinkSync(configFile); }, async abortOnStderr(data) { if (data.includes('Config contains initial errors')) { diff --git a/test/cli/samples/watch/watch-config-no-update/_config.js b/test/cli/samples/watch/watch-config-no-update/_config.js index a90cb10e14a..3f8dea95e2c 100644 --- a/test/cli/samples/watch/watch-config-no-update/_config.js +++ b/test/cli/samples/watch/watch-config-no-update/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { unlinkSync, writeFileSync } = require('fs'); const path = require('path'); const { atomicWriteFileSync } = require('../../../../utils'); @@ -17,10 +17,10 @@ module.exports = { command: 'rollup -cw', before() { configFile = path.resolve(__dirname, 'rollup.config.js'); - fs.writeFileSync(configFile, configContent); + writeFileSync(configFile, configContent); }, after() { - fs.unlinkSync(configFile); + unlinkSync(configFile); }, abortOnStderr(data) { if (data.includes(`created _actual${path.sep}main.js`)) { diff --git a/test/form/index.js b/test/form/index.js index 928a9cdd641..f0d1c6e4145 100644 --- a/test/form/index.js +++ b/test/form/index.js @@ -1,13 +1,13 @@ const assert = require('assert'); +const { existsSync, readFileSync } = require('fs'); const path = require('path'); -const sander = require('sander'); const rollup = require('../../dist/rollup'); const { normaliseOutput, runTestSuiteWithSamples } = require('../utils.js'); const FORMATS = ['amd', 'cjs', 'system', 'es', 'iife', 'umd']; runTestSuiteWithSamples('form', path.resolve(__dirname, 'samples'), (dir, config) => { - const isSingleFormatTest = sander.existsSync(dir + '/_expected.js'); + const isSingleFormatTest = existsSync(dir + '/_expected.js'); const itOrDescribe = isSingleFormatTest ? it : describe; (config.skip ? itOrDescribe.skip : config.solo ? itOrDescribe.only : itOrDescribe)( path.basename(dir) + ': ' + config.description, @@ -79,19 +79,19 @@ runTestSuiteWithSamples('form', path.resolve(__dirname, 'samples'), (dir, config async function generateAndTestBundle(bundle, outputOptions, expectedFile, { show }) { await bundle.write(outputOptions); - const actualCode = normaliseOutput(sander.readFileSync(outputOptions.file)); + const actualCode = normaliseOutput(readFileSync(outputOptions.file, 'utf8')); let expectedCode; let actualMap; let expectedMap; try { - expectedCode = normaliseOutput(sander.readFileSync(expectedFile)); + expectedCode = normaliseOutput(readFileSync(expectedFile, 'utf8')); } catch (err) { expectedCode = 'missing file'; } try { - actualMap = JSON.parse(sander.readFileSync(outputOptions.file + '.map').toString()); + actualMap = JSON.parse(readFileSync(outputOptions.file + '.map', 'utf8')); actualMap.sourcesContent = actualMap.sourcesContent ? actualMap.sourcesContent.map(normaliseOutput) : null; @@ -100,7 +100,7 @@ async function generateAndTestBundle(bundle, outputOptions, expectedFile, { show } try { - expectedMap = JSON.parse(sander.readFileSync(expectedFile + '.map').toString()); + expectedMap = JSON.parse(readFileSync(expectedFile + '.map', 'utf8')); expectedMap.sourcesContent = actualMap.sourcesContent ? expectedMap.sourcesContent.map(normaliseOutput) : null; diff --git a/test/form/samples/builtin-prototypes/array-expression/_expected.js b/test/form/samples/builtin-prototypes/array-expression/_expected.js index 4fea44f0ad9..df9bd95554b 100644 --- a/test/form/samples/builtin-prototypes/array-expression/_expected.js +++ b/test/form/samples/builtin-prototypes/array-expression/_expected.js @@ -55,6 +55,14 @@ _flatMapArray[0].effect(); const _forEachArray = [{ effect() {} }]; _forEachArray.forEach(element => (element.effect = () => console.log(1))); _forEachArray[0].effect(); +[1].groupBy(() => console.log(1) || true); +const _groupByArray = [{ effect() {} }]; +_groupByArray.groupBy((_, element) => (element.effect = () => console.log(1))); +_groupByArray[0].effect(); +[1].groupByToMap(() => console.log(1) || true); +const _groupByToMapArray = [{ effect() {} }]; +_groupByToMapArray.groupBy((_, element) => (element.effect = () => console.log(1))); +_groupByToMapArray[0].effect(); [1].map(() => console.log(1) || 1); const _mapArray = [{ effect() {} }]; _mapArray.map(element => (element.effect = () => console.log(1))); diff --git a/test/form/samples/builtin-prototypes/array-expression/main.js b/test/form/samples/builtin-prototypes/array-expression/main.js index 3cf1b47ca43..3cbf65355ac 100644 --- a/test/form/samples/builtin-prototypes/array-expression/main.js +++ b/test/form/samples/builtin-prototypes/array-expression/main.js @@ -104,6 +104,18 @@ const _forEachArray = [{ effect() {} }]; _forEachArray.forEach(element => (element.effect = () => console.log(1))); _forEachArray[0].effect(); +const _groupBy = [1].groupBy(() => true); +const _groupByEffect = [1].groupBy(() => console.log(1) || true); +const _groupByArray = [{ effect() {} }]; +_groupByArray.groupBy((_, element) => (element.effect = () => console.log(1))); +_groupByArray[0].effect(); + +const _groupByToMap = [1].groupByToMap(() => true); +const _groupByToMapEffect = [1].groupByToMap(() => console.log(1) || true); +const _groupByToMapArray = [{ effect() {} }]; +_groupByToMapArray.groupBy((_, element) => (element.effect = () => console.log(1))); +_groupByToMapArray[0].effect(); + const _map = [1].map(() => 1).join(','); const _mapEffect = [1].map(() => console.log(1) || 1); const _mapArray = [{ effect() {} }]; diff --git a/test/form/samples/deprecated/emit-asset/_config.js b/test/form/samples/deprecated/emit-asset/_config.js index 6d4c88ac239..f7930b32270 100644 --- a/test/form/samples/deprecated/emit-asset/_config.js +++ b/test/form/samples/deprecated/emit-asset/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -17,7 +17,7 @@ module.exports = { if (id.endsWith('.svg')) { return `export default import.meta.ROLLUP_ASSET_URL_${this.emitAsset( path.basename(id), - fs.readFileSync(id) + readFileSync(id) )};`; } }, @@ -30,7 +30,7 @@ module.exports = { assert.strictEqual(asset.isAsset, true); assert.strictEqual(asset.type, 'asset'); assert.ok( - asset.source.equals(fs.readFileSync(path.resolve(__dirname, 'logo.svg'))), + asset.source.equals(readFileSync(path.resolve(__dirname, 'logo.svg'))), 'asset has correct source' ); assert.ok(keys[1].endsWith('.js'), `${keys[1]} ends with ".js"`); diff --git a/test/form/samples/emit-asset-file/_config.js b/test/form/samples/emit-asset-file/_config.js index 5976fabd374..b4bbd8926d7 100644 --- a/test/form/samples/emit-asset-file/_config.js +++ b/test/form/samples/emit-asset-file/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -16,7 +16,7 @@ module.exports = { return `export default import.meta.ROLLUP_FILE_URL_${this.emitFile({ type: 'asset', name: path.basename(id), - source: fs.readFileSync(id) + source: readFileSync(id) })};`; } }, @@ -28,7 +28,7 @@ module.exports = { assert.strictEqual(asset.fileName, 'assets/logo-25585ac1.svg'); assert.strictEqual(asset.type, 'asset'); assert.ok( - asset.source.equals(fs.readFileSync(path.resolve(__dirname, 'logo.svg'))), + asset.source.equals(readFileSync(path.resolve(__dirname, 'logo.svg'))), 'asset has correct source' ); assert.ok(keys[1].endsWith('.js'), `${keys[1]} ends with ".js"`); diff --git a/test/form/samples/emit-file-tree-shaken-access/_config.js b/test/form/samples/emit-file-tree-shaken-access/_config.js index 1feae6664f4..3b52af0c675 100644 --- a/test/form/samples/emit-file-tree-shaken-access/_config.js +++ b/test/form/samples/emit-file-tree-shaken-access/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -15,7 +15,7 @@ module.exports = { return `export default import.meta.ROLLUP_FILE_URL_${this.emitFile({ type: 'asset', name: path.basename(id), - source: fs.readFileSync(id) + source: readFileSync(id) })};`; } } diff --git a/test/form/samples/merge-namespaces-non-live/_expected.js b/test/form/samples/merge-namespaces-non-live/_expected.js index 4b2dd6e372a..0ccac72f180 100644 --- a/test/form/samples/merge-namespaces-non-live/_expected.js +++ b/test/form/samples/merge-namespaces-non-live/_expected.js @@ -13,10 +13,12 @@ function _mergeNamespaces(n, m) { return Object.freeze(n); } -const __synthetic = { foo: 'foo' }; +const __synthetic$1 = { module: 'synthetic' }; + +const __synthetic = { module: 'reexport' }; var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/_mergeNamespaces({ __proto__: null -}, [__synthetic, external1, external2])); +}, [__synthetic, __synthetic$1, external1, external2])); console.log(ns); diff --git a/test/form/samples/merge-namespaces-non-live/reexport.js b/test/form/samples/merge-namespaces-non-live/reexport.js index 976fee83e08..9204c816376 100644 --- a/test/form/samples/merge-namespaces-non-live/reexport.js +++ b/test/form/samples/merge-namespaces-non-live/reexport.js @@ -1,3 +1,4 @@ export * from 'external1'; export * from './synthetic'; export * from 'external2'; +export const __synthetic = { module: 'reexport' }; diff --git a/test/form/samples/merge-namespaces-non-live/synthetic.js b/test/form/samples/merge-namespaces-non-live/synthetic.js index 7592639d278..b180a18b691 100644 --- a/test/form/samples/merge-namespaces-non-live/synthetic.js +++ b/test/form/samples/merge-namespaces-non-live/synthetic.js @@ -1 +1 @@ -export const __synthetic = { foo: 'foo' }; +export const __synthetic = { module: 'synthetic' }; diff --git a/test/form/samples/merge-namespaces/_expected.js b/test/form/samples/merge-namespaces/_expected.js index 3675738aa02..718fc549bf6 100644 --- a/test/form/samples/merge-namespaces/_expected.js +++ b/test/form/samples/merge-namespaces/_expected.js @@ -16,10 +16,12 @@ function _mergeNamespaces(n, m) { return Object.freeze(n); } -const __synthetic = { foo: 'foo' }; +const __synthetic$1 = { module: 'synthetic' }; + +const __synthetic = { module: 'reexport' }; var ns = /*#__PURE__*/Object.freeze(/*#__PURE__*/_mergeNamespaces({ __proto__: null -}, [__synthetic, external1, external2])); +}, [__synthetic, __synthetic$1, external1, external2])); console.log(ns); diff --git a/test/form/samples/merge-namespaces/reexport.js b/test/form/samples/merge-namespaces/reexport.js index 976fee83e08..9204c816376 100644 --- a/test/form/samples/merge-namespaces/reexport.js +++ b/test/form/samples/merge-namespaces/reexport.js @@ -1,3 +1,4 @@ export * from 'external1'; export * from './synthetic'; export * from 'external2'; +export const __synthetic = { module: 'reexport' }; diff --git a/test/form/samples/merge-namespaces/synthetic.js b/test/form/samples/merge-namespaces/synthetic.js index 7592639d278..b180a18b691 100644 --- a/test/form/samples/merge-namespaces/synthetic.js +++ b/test/form/samples/merge-namespaces/synthetic.js @@ -1 +1 @@ -export const __synthetic = { foo: 'foo' }; +export const __synthetic = { module: 'synthetic' }; diff --git a/test/form/samples/namespace-reexport-name/external-reexport.js b/test/form/samples/namespace-reexport-name/external-reexport.js index c4be4108af2..4953eb6e351 100644 --- a/test/form/samples/namespace-reexport-name/external-reexport.js +++ b/test/form/samples/namespace-reexport-name/external-reexport.js @@ -1,2 +1 @@ export * from 'external'; -export { conflictOverride } from 'external'; diff --git a/test/form/samples/namespace-reexport-name/overrides.js b/test/form/samples/namespace-reexport-name/overrides.js index 172928cbe16..f2cb2622f0d 100644 --- a/test/form/samples/namespace-reexport-name/overrides.js +++ b/test/form/samples/namespace-reexport-name/overrides.js @@ -1,5 +1,4 @@ -import { indirectOverride, ignoredOverride } from './external-reexport'; +import { indirectOverride } from './external-reexport'; export { directOverride as renamedDirectOverride } from './external-reexport'; export const renamedIndirectOverride = indirectOverride; -export const conflictOverride = ignoredOverride; diff --git a/test/function/samples/conflicting-reexports/namespace-import/_config.js b/test/function/samples/conflicting-reexports/namespace-import/_config.js index a83afe3ee0c..def8efa6927 100644 --- a/test/function/samples/conflicting-reexports/namespace-import/_config.js +++ b/test/function/samples/conflicting-reexports/namespace-import/_config.js @@ -6,7 +6,7 @@ module.exports = { { code: 'NAMESPACE_CONFLICT', message: - 'Conflicting namespaces: "reexport.js" re-exports "foo" from both "first.js" and "second.js" (will be ignored)', + 'Conflicting namespaces: "reexport.js" re-exports "foo" from one of the modules "first.js" and "second.js" (will be ignored)', name: 'foo', reexporter: path.join(__dirname, 'reexport.js'), sources: [path.join(__dirname, 'first.js'), path.join(__dirname, 'second.js')] diff --git a/test/function/samples/custom-loaders/_config.js b/test/function/samples/custom-loaders/_config.js index e2f0cec12b1..e9e52b0b884 100644 --- a/test/function/samples/custom-loaders/_config.js +++ b/test/function/samples/custom-loaders/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { readFileSync } = require('fs'); module.exports = { description: 'uses custom loaders, falling back to default', @@ -7,14 +7,14 @@ module.exports = { { load(id) { if (/foo\.js/.test(id)) { - return fs.readFileSync(id, 'utf-8').replace('@', 1); + return readFileSync(id, 'utf-8').replace('@', 1); } } }, { load(id) { if (/bar\.js/.test(id)) { - return fs.readFileSync(id, 'utf-8').replace('@', 2); + return readFileSync(id, 'utf-8').replace('@', 2); } } } diff --git a/test/function/samples/custom-path-resolver-on-entry/_config.js b/test/function/samples/custom-path-resolver-on-entry/_config.js index 7d49020fd95..c92be833424 100644 --- a/test/function/samples/custom-path-resolver-on-entry/_config.js +++ b/test/function/samples/custom-path-resolver-on-entry/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); const cachedModules = { @@ -25,7 +25,7 @@ module.exports = { return cachedModules[moduleId]; } - return fs.readFileSync(moduleId, 'utf-8'); + return readFileSync(moduleId, 'utf-8'); } } ] diff --git a/test/function/samples/deprecated/manual-chunks-info/_config.js b/test/function/samples/deprecated/manual-chunks-info/_config.js index c3d75ad7977..7749e615892 100644 --- a/test/function/samples/deprecated/manual-chunks-info/_config.js +++ b/test/function/samples/deprecated/manual-chunks-info/_config.js @@ -111,6 +111,7 @@ module.exports = { ], dynamicallyImportedIds: [getId('dynamic')], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], @@ -145,6 +146,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [getId('dynamic')], + hasDefaultExport: null, hasModuleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], @@ -177,6 +179,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: true, hasModuleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], @@ -260,6 +263,7 @@ module.exports = { ], dynamicallyImportedIds: ['external'], dynamicImporters: [getId('main')], + hasDefaultExport: false, hasModuleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], diff --git a/test/function/samples/export-default-anonymous-function/_config.js b/test/function/samples/export-default-anonymous-function/_config.js index 030af6269a7..14a8ec085be 100644 --- a/test/function/samples/export-default-anonymous-function/_config.js +++ b/test/function/samples/export-default-anonymous-function/_config.js @@ -1,4 +1,4 @@ -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); module.exports = { @@ -10,7 +10,7 @@ module.exports = { return path.basename(importee).replace(/\..+/, ''); }, load(id) { - return fs.readFileSync(path.join(__dirname, id + '.js'), 'utf-8'); + return readFileSync(path.join(__dirname, id + '.js'), 'utf-8'); } } ] diff --git a/test/function/samples/has-default-export/_config.js b/test/function/samples/has-default-export/_config.js new file mode 100644 index 00000000000..daea5948706 --- /dev/null +++ b/test/function/samples/has-default-export/_config.js @@ -0,0 +1,45 @@ +const assert = require('assert'); +const path = require('path'); + +module.exports = { + description: 'reports if a module has a default export', + options: { + plugins: [ + { + async buildStart() { + const ID_MAIN = path.join(__dirname, 'main.js'); + const loadMain = this.load({ id: ID_MAIN }); + assert.strictEqual(this.getModuleInfo(ID_MAIN).hasDefaultExport, null); + assert.strictEqual((await loadMain).hasDefaultExport, false); + + assert.strictEqual( + (await this.load({ id: path.join(__dirname, 'direct.js') })).hasDefaultExport, + true, + 'direct' + ); + assert.strictEqual( + (await this.load({ id: path.join(__dirname, 'indirect.js') })).hasDefaultExport, + true, + 'indirect' + ); + assert.strictEqual( + (await this.load({ id: path.join(__dirname, 'reexport1.js') })).hasDefaultExport, + true, + 'reexport' + ); + assert.strictEqual( + (await this.load({ id: path.join(__dirname, 'reexport2.js') })).hasDefaultExport, + true, + 'renamed reexport' + ); + }, + load(id) { + assert.strictEqual(this.getModuleInfo(id).hasDefaultExport, null, `load ${id}`); + }, + transform(code, id) { + assert.strictEqual(this.getModuleInfo(id).hasDefaultExport, null, `transform ${id}`); + } + } + ] + } +}; diff --git a/test/function/samples/has-default-export/direct.js b/test/function/samples/has-default-export/direct.js new file mode 100644 index 00000000000..f2f6d5c381b --- /dev/null +++ b/test/function/samples/has-default-export/direct.js @@ -0,0 +1 @@ +export default 'direct'; diff --git a/test/function/samples/has-default-export/indirect.js b/test/function/samples/has-default-export/indirect.js new file mode 100644 index 00000000000..28786796a99 --- /dev/null +++ b/test/function/samples/has-default-export/indirect.js @@ -0,0 +1,2 @@ +const indirect = 'indirect'; +export { indirect as default }; diff --git a/test/function/samples/has-default-export/main.js b/test/function/samples/has-default-export/main.js new file mode 100644 index 00000000000..28d6ee09c9c --- /dev/null +++ b/test/function/samples/has-default-export/main.js @@ -0,0 +1,9 @@ +import direct from './direct.js'; +import indirect from './indirect.js'; +import reexport1 from './reexport1.js'; +import reexport2 from './reexport2.js'; + +assert.strictEqual(direct, 'direct'); +assert.strictEqual(indirect, 'indirect'); +assert.strictEqual(reexport1, 'default'); +assert.strictEqual(reexport2, 'foo'); diff --git a/test/function/samples/has-default-export/other.js b/test/function/samples/has-default-export/other.js new file mode 100644 index 00000000000..a99ac05aa02 --- /dev/null +++ b/test/function/samples/has-default-export/other.js @@ -0,0 +1,2 @@ +export default 'default'; +export const foo = 'foo'; diff --git a/test/function/samples/has-default-export/reexport1.js b/test/function/samples/has-default-export/reexport1.js new file mode 100644 index 00000000000..b98cf73cdb9 --- /dev/null +++ b/test/function/samples/has-default-export/reexport1.js @@ -0,0 +1 @@ +export { default } from './other.js'; diff --git a/test/function/samples/has-default-export/reexport2.js b/test/function/samples/has-default-export/reexport2.js new file mode 100644 index 00000000000..08facccd528 --- /dev/null +++ b/test/function/samples/has-default-export/reexport2.js @@ -0,0 +1 @@ +export { foo as default } from './other.js'; diff --git a/test/function/samples/load-resolve-dependencies/_config.js b/test/function/samples/load-resolve-dependencies/_config.js new file mode 100644 index 00000000000..dcf1a498f7d --- /dev/null +++ b/test/function/samples/load-resolve-dependencies/_config.js @@ -0,0 +1,93 @@ +const assert = require('assert'); +const path = require('path'); +const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:'; +const chunks = []; + +module.exports = { + description: 'allows to wait for dependency resolution in this.load to scan dependency trees', + context: { chunks }, + async exports(exports) { + assert.deepStrictEqual(chunks, []); + const { importSecond } = await exports.importFirst(); + const expectedFirstChunk = ['first.js', 'second.js', 'third.js'].map(name => + path.join(__dirname, name) + ); + assert.deepStrictEqual(chunks, [expectedFirstChunk]); + await importSecond(); + const expectedSecondChunk = ['second.js', 'third.js'].map(name => path.join(__dirname, name)); + assert.deepStrictEqual(chunks, [expectedFirstChunk, expectedSecondChunk]); + }, + options: { + plugins: [ + { + name: 'add-chunk-log', + async resolveDynamicImport(specifier, importer) { + // Ignore non-static targets + if (!(typeof specifier === 'string')) return; + // Get the id and initial meta information of the import target + const resolved = await this.resolve(specifier, importer); + // Ignore external targets. Explicit externals have the "external" + // property while unresolved imports are "null". + if (resolved && !resolved.external) { + // We trigger loading the module without waiting for it here + // because meta information attached by resolveId hooks (that may + // be contained in "resolved") is only attached to a module the + // first time it is loaded. + // That guarantees this meta information, that plugins like + // commonjs may depend upon, is not lost even if we use "this.load" + // with just the id in the load hook. + this.load(resolved); + return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`; + } + }, + async load(id) { + // Ignore all files but our dynamic import proxies + if (!id.startsWith('\0dynamic-import:')) return null; + const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length); + // To allow loading modules in parallel while keeping complexity low, + // we do not directly await each "this.load" call but put their + // promises into an array where we await each entry via an async for + // loop. + const moduleInfoPromises = [this.load({ id: actualId, resolveDependencies: true })]; + // We track each loaded dependency here so that we do not load a file + // twice and also do not get stuck when there are circular + // dependencies. + const dependencies = new Set([actualId]); + // "importedResolution" tracks the objects created via "resolveId". + // Again we are using those instead of "importedIds" so that + // important meta information is not lost. + for await (const { importedIdResolutions } of moduleInfoPromises) { + for (const resolved of importedIdResolutions) { + if (!dependencies.has(resolved.id)) { + dependencies.add(resolved.id); + moduleInfoPromises.push(this.load({ ...resolved, resolveDependencies: true })); + } + } + } + let code = `chunks.push([${[...dependencies] + .map(JSON.stringify) + .join(', ')}]); export * from ${JSON.stringify(actualId)};`; + // Namespace reexports do not reexport default exports, which is why + // we reexport it manually if it exists + if (this.getModuleInfo(actualId).hasDefaultExport) { + code += `export { default } from ${JSON.stringify(actualId)};`; + } + return code; + }, + async resolveId() { + // We delay resolution just slightly so that we can see the effect of + // resolveDependencies + return new Promise(resolve => setTimeout(() => resolve(null), 10)); + } + } + ] + }, + warnings: [ + { + code: 'CIRCULAR_DEPENDENCY', + cycle: ['second.js', 'third.js', 'second.js'], + importer: 'second.js', + message: 'Circular dependency: second.js -> third.js -> second.js' + } + ] +}; diff --git a/test/function/samples/load-resolve-dependencies/first.js b/test/function/samples/load-resolve-dependencies/first.js new file mode 100644 index 00000000000..df407ed8c89 --- /dev/null +++ b/test/function/samples/load-resolve-dependencies/first.js @@ -0,0 +1,3 @@ +import './second.js'; +import './third.js'; +export const importSecond = () => import('./second.js'); diff --git a/test/function/samples/load-resolve-dependencies/main.js b/test/function/samples/load-resolve-dependencies/main.js new file mode 100644 index 00000000000..c39b1a72fdb --- /dev/null +++ b/test/function/samples/load-resolve-dependencies/main.js @@ -0,0 +1 @@ +export const importFirst = () => import('./first.js') \ No newline at end of file diff --git a/test/function/samples/load-resolve-dependencies/second.js b/test/function/samples/load-resolve-dependencies/second.js new file mode 100644 index 00000000000..11611eedf50 --- /dev/null +++ b/test/function/samples/load-resolve-dependencies/second.js @@ -0,0 +1 @@ +import './third.js'; diff --git a/test/function/samples/load-resolve-dependencies/third.js b/test/function/samples/load-resolve-dependencies/third.js new file mode 100644 index 00000000000..f986185781a --- /dev/null +++ b/test/function/samples/load-resolve-dependencies/third.js @@ -0,0 +1 @@ +import './second.js'; diff --git a/test/function/samples/manual-chunks-info/_config.js b/test/function/samples/manual-chunks-info/_config.js index 4780bfcae47..b30f082d6e0 100644 --- a/test/function/samples/manual-chunks-info/_config.js +++ b/test/function/samples/manual-chunks-info/_config.js @@ -110,6 +110,7 @@ module.exports = { ], dynamicallyImportedIds: [getId('dynamic')], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: getId('main'), implicitlyLoadedAfterOneOf: [], @@ -144,6 +145,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [getId('dynamic')], + hasDefaultExport: null, hasModuleSideEffects: true, id: 'external', implicitlyLoadedAfterOneOf: [], @@ -176,6 +178,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: true, hasModuleSideEffects: true, id: getId('lib'), implicitlyLoadedAfterOneOf: [], @@ -259,6 +262,7 @@ module.exports = { ], dynamicallyImportedIds: ['external'], dynamicImporters: [getId('main')], + hasDefaultExport: false, hasModuleSideEffects: true, id: getId('dynamic'), implicitlyLoadedAfterOneOf: [], diff --git a/test/function/samples/module-parsed-hook/_config.js b/test/function/samples/module-parsed-hook/_config.js index c1505d17f54..75e093d2e4d 100644 --- a/test/function/samples/module-parsed-hook/_config.js +++ b/test/function/samples/module-parsed-hook/_config.js @@ -51,6 +51,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], @@ -107,6 +108,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_DEP, implicitlyLoadedAfterOneOf: [], diff --git a/test/function/samples/namespace-override/_config.js b/test/function/samples/namespace-override/_config.js new file mode 100644 index 00000000000..e8fd15a1f05 --- /dev/null +++ b/test/function/samples/namespace-override/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'does not warn when overriding namespace reexports with explicit ones' +}; diff --git a/test/function/samples/namespace-override/a.js b/test/function/samples/namespace-override/a.js new file mode 100644 index 00000000000..4ef4d03891d --- /dev/null +++ b/test/function/samples/namespace-override/a.js @@ -0,0 +1,5 @@ +export const a = 'a'; +export const aExportOverride = 'a'; +export const aReexportOverride = 'a'; +export const hiddenConflictByExport = 'a'; +export const hiddenConflictByReexport = 'a'; diff --git a/test/function/samples/namespace-override/b.js b/test/function/samples/namespace-override/b.js new file mode 100644 index 00000000000..ac9e1189e42 --- /dev/null +++ b/test/function/samples/namespace-override/b.js @@ -0,0 +1,4 @@ +export const b = 'b'; +export const hiddenConflictByExport = 'b'; +export const hiddenConflictByReexport = 'b'; +export * from './a.js'; diff --git a/test/function/samples/namespace-override/exportAll.js b/test/function/samples/namespace-override/exportAll.js new file mode 100644 index 00000000000..229f7033593 --- /dev/null +++ b/test/function/samples/namespace-override/exportAll.js @@ -0,0 +1,5 @@ +export * from './a.js'; +export * from './b.js'; +export const aExportOverride = 'override'; +export const hiddenConflictByExport = 'hidden'; +export { b as aReexportOverride, b as hiddenConflictByReexport } from './b.js'; diff --git a/test/function/samples/namespace-override/main.js b/test/function/samples/namespace-override/main.js new file mode 100644 index 00000000000..0ad3e6b4278 --- /dev/null +++ b/test/function/samples/namespace-override/main.js @@ -0,0 +1,12 @@ +import * as ns from './exportAll.js'; +import { hiddenConflictByExport, hiddenConflictByReexport } from './exportAll.js'; + +assert.deepStrictEqual(ns, { + __proto__: null, + a: 'a', + aExportOverride: 'override', + aReexportOverride: 'b', + b: 'b', + hiddenConflictByExport: 'hidden', + hiddenConflictByReexport: 'b', +}); diff --git a/test/function/samples/plugin-module-information/_config.js b/test/function/samples/plugin-module-information/_config.js index 67a5202770d..f5ad0b6e12a 100644 --- a/test/function/samples/plugin-module-information/_config.js +++ b/test/function/samples/plugin-module-information/_config.js @@ -18,6 +18,7 @@ module.exports = { ast: null, code: null, dynamicImporters: [], + hasDefaultExport: null, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], hasModuleSideEffects: true, @@ -183,6 +184,7 @@ module.exports = { ], dynamicallyImportedIds: [ID_NESTED, ID_PATH], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_MAIN, implicitlyLoadedAfterOneOf: [], @@ -270,6 +272,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_FOO, implicitlyLoadedAfterOneOf: [], @@ -297,6 +300,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [ID_MAIN], + hasDefaultExport: null, hasModuleSideEffects: true, id: ID_PATH, implicitlyLoadedAfterOneOf: [], @@ -379,6 +383,7 @@ module.exports = { dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], dynamicImporters: [ID_MAIN], + hasDefaultExport: false, hasModuleSideEffects: true, id: ID_NESTED, implicitlyLoadedAfterOneOf: [], diff --git a/test/function/samples/preload-module/_config.js b/test/function/samples/preload-module/_config.js index 15ce55e3c24..51ec568846e 100644 --- a/test/function/samples/preload-module/_config.js +++ b/test/function/samples/preload-module/_config.js @@ -33,6 +33,7 @@ module.exports = { assert.deepStrictEqual(moduleInfo, { code: "import './dep';\nassert.ok(true);\n", dynamicImporters: [], + hasDefaultExport: false, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], hasModuleSideEffects: true, @@ -73,6 +74,7 @@ module.exports = { assert.deepStrictEqual(moduleInfo, { code: 'assert.ok(true);\n', dynamicImporters: [], + hasDefaultExport: false, dynamicallyImportedIdResolutions: [], dynamicallyImportedIds: [], hasModuleSideEffects: true, diff --git a/test/function/samples/shims-missing-exports/dep2.js b/test/function/samples/shims-missing-exports/dep2.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/function/samples/shims-missing-exports/main.js b/test/function/samples/shims-missing-exports/main.js index cc652c34e2e..7db98642c1c 100644 --- a/test/function/samples/shims-missing-exports/main.js +++ b/test/function/samples/shims-missing-exports/main.js @@ -1,3 +1,3 @@ import { missing } from './dep1.js'; -export {missing} +export { missing }; diff --git a/test/function/samples/synthetic-named-export-entry/_config.js b/test/function/samples/synthetic-named-export-entry/_config.js new file mode 100644 index 00000000000..9722c598584 --- /dev/null +++ b/test/function/samples/synthetic-named-export-entry/_config.js @@ -0,0 +1,39 @@ +const assert = require('assert'); +const path = require('path'); +const ID_MAIN = path.join(__dirname, 'main.js'); +const ID_OVERRIDE = path.join(__dirname, 'override.js'); +const ID_NOOVERRIDE = path.join(__dirname, 'noOverride.js'); +const ID_HIDDENNAMESPACE = path.join(__dirname, 'hiddenNamespace.js'); + +module.exports = { + description: 'does not expose synthetic named exports on entry points', + options: { + plugins: [ + { + transform(code, id) { + switch (id) { + case ID_MAIN: + return { syntheticNamedExports: 'synthMain' }; + case ID_OVERRIDE: + return { syntheticNamedExports: 'synthOverride' }; + case ID_NOOVERRIDE: + return { syntheticNamedExports: 'synthNoOverride' }; + case ID_HIDDENNAMESPACE: + return { syntheticNamedExports: 'synthHiddenNamespace' }; + } + } + } + ] + }, + exports(exports) { + assert.deepStrictEqual(exports, { + explicitReexport: { override: true }, + hiddenNamespace: 'hiddenNamespace', + main: 'main', + noOverride: 'noOverride', + override: 'override', + synthHiddenNamespace: 'hidden in override', + synthOverride: 'overridden' + }); + } +}; diff --git a/test/function/samples/synthetic-named-export-entry/hiddenNamespace.js b/test/function/samples/synthetic-named-export-entry/hiddenNamespace.js new file mode 100644 index 00000000000..97e81d68bdc --- /dev/null +++ b/test/function/samples/synthetic-named-export-entry/hiddenNamespace.js @@ -0,0 +1,2 @@ +export const hiddenNamespace = 'hiddenNamespace'; +export const synthHiddenNamespace = { hiddenNamespace: true }; diff --git a/test/function/samples/synthetic-named-export-entry/main.js b/test/function/samples/synthetic-named-export-entry/main.js new file mode 100644 index 00000000000..cc7963f49ae --- /dev/null +++ b/test/function/samples/synthetic-named-export-entry/main.js @@ -0,0 +1,7 @@ +export const main = 'main'; +export const synthMain = { main: true }; +export * from './noOverride.js'; +export * from './override.js'; +export * from './hiddenNamespace.js'; +export const synthOverride = 'overridden'; +export { synthOverride as explicitReexport } from './override.js'; diff --git a/test/function/samples/synthetic-named-export-entry/noOverride.js b/test/function/samples/synthetic-named-export-entry/noOverride.js new file mode 100644 index 00000000000..205e87552a5 --- /dev/null +++ b/test/function/samples/synthetic-named-export-entry/noOverride.js @@ -0,0 +1,2 @@ +export const noOverride = 'noOverride'; +export const synthNoOverride = { noOverride: true }; diff --git a/test/function/samples/synthetic-named-export-entry/override.js b/test/function/samples/synthetic-named-export-entry/override.js new file mode 100644 index 00000000000..8f40d7ee6ca --- /dev/null +++ b/test/function/samples/synthetic-named-export-entry/override.js @@ -0,0 +1,3 @@ +export const override = 'override'; +export const synthOverride = { override: true }; +export const synthHiddenNamespace = 'hidden in override'; diff --git a/test/function/samples/synthetic-named-exports/circular-synthetic-exports/_config.js b/test/function/samples/synthetic-named-exports/circular-synthetic-exports/_config.js index 35a95977898..89a605bae77 100644 --- a/test/function/samples/synthetic-named-exports/circular-synthetic-exports/_config.js +++ b/test/function/samples/synthetic-named-exports/circular-synthetic-exports/_config.js @@ -15,7 +15,7 @@ module.exports = { error: { code: 'SYNTHETIC_NAMED_EXPORTS_NEED_NAMESPACE_EXPORT', id: path.join(__dirname, 'main.js'), - message: `Module "main.js" that is marked with 'syntheticNamedExports: "__synthetic"' needs an export named "__synthetic" that does not reexport an unresolved named export of the same module.`, + message: `Module "main.js" that is marked with 'syntheticNamedExports: "__synthetic"' needs an explicit export named "__synthetic" that does not reexport an unresolved named export of the same module.`, watchFiles: [path.join(__dirname, 'main.js'), path.join(__dirname, 'dep.js')] } }; diff --git a/test/function/samples/synthetic-named-exports/synthetic-exports-need-fallback-export/_config.js b/test/function/samples/synthetic-named-exports/synthetic-exports-need-fallback-export/_config.js index a11192c4697..7e85c3165ac 100644 --- a/test/function/samples/synthetic-named-exports/synthetic-exports-need-fallback-export/_config.js +++ b/test/function/samples/synthetic-named-exports/synthetic-exports-need-fallback-export/_config.js @@ -17,7 +17,7 @@ module.exports = { error: { code: 'SYNTHETIC_NAMED_EXPORTS_NEED_NAMESPACE_EXPORT', id: DEP_ID, - message: `Module "dep.js" that is marked with 'syntheticNamedExports: "__synthetic"' needs an export named "__synthetic" that does not reexport an unresolved named export of the same module.`, + message: `Module "dep.js" that is marked with 'syntheticNamedExports: "__synthetic"' needs an explicit export named "__synthetic" that does not reexport an unresolved named export of the same module.`, watchFiles: [path.join(__dirname, 'main.js'), DEP_ID] } }; diff --git a/test/function/samples/transform-without-code/_config.js b/test/function/samples/transform-without-code/_config.js index dc9d1447d13..cfe8d21e839 100644 --- a/test/function/samples/transform-without-code/_config.js +++ b/test/function/samples/transform-without-code/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); const { SourceMapConsumer } = require('source-map'); @@ -21,7 +21,7 @@ module.exports = { const smc = await new SourceMapConsumer(map); const originalLoc = smc.originalPositionFor({ line, column }); assert.notStrictEqual(originalLoc.line, null); - const originalCode = fs.readFileSync(path.join(__dirname, 'main.js'), 'utf8'); + const originalCode = readFileSync(path.join(__dirname, 'main.js'), 'utf8'); assert.strictEqual( originalCode.split('\n')[originalLoc.line - 1].substr(originalLoc.column, 2), '42' diff --git a/test/function/samples/warn-on-namespace-conflict/_config.js b/test/function/samples/warn-on-namespace-conflict/_config.js index bf046da2768..0f6139a3501 100644 --- a/test/function/samples/warn-on-namespace-conflict/_config.js +++ b/test/function/samples/warn-on-namespace-conflict/_config.js @@ -7,8 +7,8 @@ module.exports = { code: 'NAMESPACE_CONFLICT', name: 'foo', reexporter: path.join(__dirname, 'main.js'), - sources: [path.join(__dirname, 'foo.js'), path.join(__dirname, 'deep.js')], - message: `Conflicting namespaces: "main.js" re-exports "foo" from both "foo.js" and "deep.js" (will be ignored)` + sources: [path.join(__dirname, 'foo.js'), path.join(__dirname, 'bar.js')], + message: `Conflicting namespaces: "main.js" re-exports "foo" from one of the modules "foo.js" and "bar.js" (will be ignored)` } ] }; diff --git a/test/hooks/index.js b/test/hooks/index.js index d0f04609ae1..b776bb9e808 100644 --- a/test/hooks/index.js +++ b/test/hooks/index.js @@ -1,6 +1,7 @@ const assert = require('assert'); +const { readdirSync } = require('fs'); const path = require('path'); -const sander = require('sander'); +const { removeSync } = require('fs-extra'); const rollup = require('../../dist/rollup.js'); const { loader } = require('../utils.js'); @@ -33,9 +34,9 @@ describe('hooks', () => { }) ) .then(() => { - const fileNames = sander.readdirSync(TEMP_DIR).sort(); + const fileNames = readdirSync(TEMP_DIR).sort(); assert.deepStrictEqual(fileNames, ['chunk.js', 'input.js']); - return sander.rimraf(TEMP_DIR); + return removeSync(TEMP_DIR); })); it('supports buildStart and buildEnd hooks', () => { @@ -574,7 +575,7 @@ describe('hooks', () => { .then(bundle => bundle.write({ format: 'es', file })) .then(() => { assert.strictEqual(callCount, 1); - return sander.rimraf(TEMP_DIR); + return removeSync(TEMP_DIR); }); }); diff --git a/test/sourcemaps/samples/combined-sourcemap-with-loader/_config.js b/test/sourcemaps/samples/combined-sourcemap-with-loader/_config.js index 1f670313146..c9d16748ce3 100644 --- a/test/sourcemaps/samples/combined-sourcemap-with-loader/_config.js +++ b/test/sourcemaps/samples/combined-sourcemap-with-loader/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const buble = require('buble'); const MagicString = require('magic-string'); const { SourceMapConsumer } = require('source-map'); @@ -11,7 +11,7 @@ module.exports = { plugins: [ { load(id) { - const code = fs.readFileSync(id, 'utf-8'); + const code = readFileSync(id, 'utf-8'); const out = buble.transform(code, { transforms: { modules: false }, sourceMap: true, diff --git a/test/sourcemaps/samples/excludes-plugin-helpers/_config.js b/test/sourcemaps/samples/excludes-plugin-helpers/_config.js index 393c9e825a3..e64a91e702f 100644 --- a/test/sourcemaps/samples/excludes-plugin-helpers/_config.js +++ b/test/sourcemaps/samples/excludes-plugin-helpers/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); const HELPER = '\0helper'; @@ -18,7 +18,7 @@ module.exports = { load(id) { if (id === HELPER) { - return fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); + return readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); } } } diff --git a/test/sourcemaps/samples/loaders/_config.js b/test/sourcemaps/samples/loaders/_config.js index 9e45fa58ec9..7df0a3b4fef 100644 --- a/test/sourcemaps/samples/loaders/_config.js +++ b/test/sourcemaps/samples/loaders/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const buble = require('buble'); const { SourceMapConsumer } = require('source-map'); const getLocation = require('../../getLocation'); @@ -16,7 +16,7 @@ module.exports = { id = id.replace(/bar.js$/, 'foo.js'); } - const code = fs.readFileSync(id, 'utf-8'); + const code = readFileSync(id, 'utf-8'); const out = buble.transform(code, { transforms: { modules: false }, diff --git a/test/sourcemaps/samples/reified-namespace/_config.js b/test/sourcemaps/samples/reified-namespace/_config.js index 431758d276a..09fd70e39f5 100644 --- a/test/sourcemaps/samples/reified-namespace/_config.js +++ b/test/sourcemaps/samples/reified-namespace/_config.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); const { SourceMapConsumer } = require('source-map'); const getLocation = require('../../getLocation'); @@ -9,7 +9,7 @@ module.exports = { async test(code, map) { const smc = await new SourceMapConsumer(map); - const main = fs.readFileSync(path.join(__dirname, 'main.js'), 'utf-8'); + const main = readFileSync(path.join(__dirname, 'main.js'), 'utf-8'); const generatedLoc = getLocation(code, 'deepEqual'); const actual = smc.originalPositionFor(generatedLoc); diff --git a/test/sourcemaps/samples/single-length-segments/_config.js b/test/sourcemaps/samples/single-length-segments/_config.js index 1b38deb956d..7850b91ad7a 100644 --- a/test/sourcemaps/samples/single-length-segments/_config.js +++ b/test/sourcemaps/samples/single-length-segments/_config.js @@ -1,10 +1,10 @@ const assert = require('assert'); -const fs = require('fs'); +const { readFileSync } = require('fs'); const path = require('path'); const { SourceMapConsumer } = require('source-map'); const getLocation = require('../../getLocation'); -const original = fs.readFileSync(path.resolve(__dirname, 'main.js'), 'utf-8'); +const original = readFileSync(path.resolve(__dirname, 'main.js'), 'utf-8'); module.exports = { description: 'handles single-length sourcemap segments', @@ -13,8 +13,8 @@ module.exports = { { transform() { return { - code: fs.readFileSync(path.resolve(__dirname, 'output.js'), 'utf-8'), - map: fs.readFileSync(path.resolve(__dirname, 'output.js.map'), 'utf-8') + code: readFileSync(path.resolve(__dirname, 'output.js'), 'utf-8'), + map: readFileSync(path.resolve(__dirname, 'output.js.map'), 'utf-8') }; } } diff --git a/test/utils.js b/test/utils.js index 7d391538477..57854e5c41c 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,8 +1,18 @@ const assert = require('assert'); -const fs = require('fs'); -const path = require('path'); +const { + closeSync, + fsyncSync, + openSync, + readdirSync, + renameSync, + unlinkSync, + writeFileSync, + writeSync +} = require('fs'); +const { basename, join } = require('path'); +const { platform, version } = require('process'); const fixturify = require('fixturify'); -const sander = require('sander'); +const { removeSync } = require('fs-extra'); exports.compareError = compareError; exports.compareWarnings = compareWarnings; @@ -124,11 +134,11 @@ function runSamples(samplesDir, runTest, onTeardown) { if (onTeardown) { afterEach(onTeardown); } - sander - .readdirSync(samplesDir) + + readdirSync(samplesDir) .filter(name => name[0] !== '.') .sort() - .forEach(fileName => runTestsInDir(path.join(samplesDir, fileName), runTest)); + .forEach(fileName => runTestsInDir(join(samplesDir, fileName), runTest)); } function runTestsInDir(dir, runTest) { @@ -137,26 +147,26 @@ function runTestsInDir(dir, runTest) { loadConfigAndRunTest(dir, runTest); } else if (fileNames.length === 0) { console.warn(`Removing empty test directory ${dir}`); - sander.rmdirSync(dir); + removeSync(dir); } else { - describe(path.basename(dir), () => { + describe(basename(dir), () => { fileNames .filter(name => name[0] !== '.') .sort() - .forEach(fileName => runTestsInDir(path.join(dir, fileName), runTest)); + .forEach(fileName => runTestsInDir(join(dir, fileName), runTest)); }); } } function getFileNamesAndRemoveOutput(dir) { try { - return sander.readdirSync(dir).filter(fileName => { + return readdirSync(dir).filter(fileName => { if (fileName === '_actual') { - sander.rimrafSync(path.join(dir, '_actual')); + removeSync(join(dir, '_actual')); return false; } if (fileName === '_actual.js') { - sander.unlinkSync(path.join(dir, '_actual.js')); + unlinkSync(join(dir, '_actual.js')); return false; } return true; @@ -172,15 +182,15 @@ function getFileNamesAndRemoveOutput(dir) { } function loadConfigAndRunTest(dir, runTest) { - const configFile = path.join(dir, '_config.js'); + const configFile = join(dir, '_config.js'); const config = require(configFile); if (!config || !config.description) { throw new Error(`Found invalid config without description: ${configFile}`); } if ( - (!config.skipIfWindows || process.platform !== 'win32') && - (!config.onlyWindows || process.platform === 'win32') && - (!config.minNodeVersion || config.minNodeVersion <= Number(/^v(\d+)/.exec(process.version)[1])) + (!config.skipIfWindows || platform !== 'win32') && + (!config.onlyWindows || platform === 'win32') && + (!config.minNodeVersion || config.minNodeVersion <= Number(/^v(\d+)/.exec(version)[1])) ) { runTest(dir, config); } @@ -231,16 +241,16 @@ function assertIncludes(actual, expected) { // if the content being overwritten is identical. function atomicWriteFileSync(filePath, contents) { const stagingPath = filePath + '_'; - fs.writeFileSync(stagingPath, contents); - fs.renameSync(stagingPath, filePath); + writeFileSync(stagingPath, contents); + renameSync(stagingPath, filePath); } // It appears that on MacOS, it sometimes takes long for the file system to update function writeAndSync(filePath, contents) { - const file = fs.openSync(filePath, 'w'); - fs.writeSync(file, contents); - fs.fsyncSync(file); - fs.closeSync(file); + const file = openSync(filePath, 'w'); + writeSync(file, contents); + fsyncSync(file); + closeSync(file); } // Sometimes, watchers on MacOS do not seem to fire. In those cases, it helps diff --git a/test/watch/index.js b/test/watch/index.js index 64b66b5cb97..648f3e819b7 100644 --- a/test/watch/index.js +++ b/test/watch/index.js @@ -1,11 +1,18 @@ const assert = require('assert'); -const path = require('path'); -const sander = require('sander'); +const { + existsSync, + promises, + readdirSync, + readFileSync, + unlinkSync, + writeFileSync +} = require('fs'); +const { resolve } = require('path'); +const { chdir, cwd, hrtime } = require('process'); +const { copy, removeSync } = require('fs-extra'); const rollup = require('../../dist/rollup'); const { atomicWriteFileSync } = require('../utils'); -const cwd = process.cwd(); - function wait(ms) { return new Promise(fulfil => { setTimeout(fulfil, ms); @@ -16,8 +23,8 @@ describe('rollup.watch', () => { let watcher; beforeEach(() => { - process.chdir(cwd); - return sander.rimraf('test/_tmp'); + chdir(cwd()); + return removeSync('test/_tmp'); }); afterEach(() => { @@ -68,118 +75,112 @@ describe('rollup.watch', () => { } function getTimeDiffInMs(previous) { - const [seconds, nanoseconds] = process.hrtime(previous); + const [seconds, nanoseconds] = hrtime(previous); return seconds * 1e3 + nanoseconds / 1e6; } it('watches a file and triggers reruns if necessary', () => { let triggerRestart = false; - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - options(options) { - assert.strictEqual(this.meta.watchMode, true, 'watchMode in options'); - }, - transform(code) { - assert.strictEqual(this.meta.watchMode, true, 'watchMode in transform'); - if (triggerRestart) { - triggerRestart = false; - return wait(100) - .then(() => atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;')) - .then(() => wait(100)) - .then(() => code); - } - } - }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - triggerRestart = true; - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + options() { + assert.strictEqual(this.meta.watchMode, true, 'watchMode in options'); }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 44); + transform(code) { + assert.strictEqual(this.meta.watchMode, true, 'watchMode in transform'); + if (triggerRestart) { + triggerRestart = false; + return wait(100) + .then(() => atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;')) + .then(() => wait(100)) + .then(() => code); + } } - ]); + }, + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + triggerRestart = true; + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 44); + } + ]); + }); }); it('does not fail for virtual files', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - resolveId(id) { - if (id === 'virtual') { - return id; - } - }, - load(id) { - if (id === 'virtual') { - return `export const value = 42;`; - } + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + resolveId(id) { + if (id === 'virtual') { + return id; } }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - atomicWriteFileSync( - 'test/_tmp/input/main.js', - "import {value} from 'virtual';\nexport default value + 1;" - ); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + load(id) { + if (id === 'virtual') { + return `export const value = 42;`; + } } - ]); + }, + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + atomicWriteFileSync( + 'test/_tmp/input/main.js', + "import {value} from 'virtual';\nexport default value + 1;" + ); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('passes file events to the watchChange plugin hook once for each change', async () => { let watchChangeCnt = 0; - await sander.copydir('test/watch/samples/basic').to('test/_tmp/input'); + await copy('test/watch/samples/basic', 'test/_tmp/input'); await wait(100); watcher = rollup.watch({ input: 'test/_tmp/input/main.js', @@ -191,7 +192,7 @@ describe('rollup.watch', () => { plugins: { watchChange(id) { watchChangeCnt++; - assert.strictEqual(id, path.resolve('test/_tmp/input/main.js')); + assert.strictEqual(id, resolve('test/_tmp/input/main.js')); } } }); @@ -236,12 +237,12 @@ describe('rollup.watch', () => { }); it('passes change parameter to the watchChange plugin hook', async () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); + const WATCHED_ID = resolve('test/_tmp/input/watched'); const events = []; let ids; - const expectedIds = [WATCHED_ID, path.resolve('test/_tmp/input/main.js')]; - await sander.copydir('test/watch/samples/watch-files').to('test/_tmp/input'); - await sander.unlink(WATCHED_ID); + const expectedIds = [WATCHED_ID, resolve('test/_tmp/input/main.js')]; + await copy('test/watch/samples/watch-files', 'test/_tmp/input'); + await promises.unlink(WATCHED_ID); await wait(100); watcher = rollup.watch({ input: 'test/_tmp/input/main.js', @@ -296,7 +297,7 @@ describe('rollup.watch', () => { assert.strictEqual(run('../_tmp/output/bundle.js'), 42); assert.deepStrictEqual(events, ['create', 'update']); assert.deepStrictEqual(ids, expectedIds); - sander.unlinkSync(WATCHED_ID); + unlinkSync(WATCHED_ID); }, 'START', 'BUNDLE_START', @@ -311,10 +312,10 @@ describe('rollup.watch', () => { }); it('correctly rewrites change event during build delay', async () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); - const MAIN_ID = path.resolve('test/_tmp/input/main.js'); + const WATCHED_ID = resolve('test/_tmp/input/watched'); + const MAIN_ID = resolve('test/_tmp/input/main.js'); let lastEvent = null; - await sander.copydir('test/watch/samples/watch-files').to('test/_tmp/input'); + await copy('test/watch/samples/watch-files', 'test/_tmp/input'); await wait(100); watcher = rollup.watch({ input: 'test/_tmp/input/main.js', @@ -351,7 +352,7 @@ describe('rollup.watch', () => { assert.strictEqual(lastEvent, null); atomicWriteFileSync(WATCHED_ID, 'another'); await wait(100); - sander.unlinkSync(WATCHED_ID); + unlinkSync(WATCHED_ID); }, 'START', 'BUNDLE_START', @@ -362,7 +363,7 @@ describe('rollup.watch', () => { lastEvent = null; atomicWriteFileSync(WATCHED_ID, '123'); await wait(100); - sander.unlinkSync(WATCHED_ID); + unlinkSync(WATCHED_ID); // To ensure there is always another change to trigger a rebuild atomicWriteFileSync(MAIN_ID, 'export default 43;'); }, @@ -390,589 +391,545 @@ describe('rollup.watch', () => { let calls = 0; let ctx1; let ctx2; - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: [ + { + buildStart() { + ctx1 = this; + }, + closeWatcher() { + assert.strictEqual(ctx1, this); + calls++; + } }, - plugins: [ - { - buildStart() { - ctx1 = this; - }, - closeWatcher() { - assert.strictEqual(ctx1, this); - calls++; - } + { + buildStart() { + ctx2 = this; }, - { - buildStart() { - ctx2 = this; - }, - closeWatcher() { - assert.strictEqual(ctx2, this); - calls++; - } + closeWatcher() { + assert.strictEqual(ctx2, this); + calls++; } - ] - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - assert.ok(ctx1); - assert.ok(ctx2); - watcher.once('close', () => { - assert.strictEqual(calls, 2); - }); - watcher.close(); } - ]); + ] }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + assert.ok(ctx1); + assert.ok(ctx2); + watcher.once('close', () => { + assert.strictEqual(calls, 2); + }); + watcher.close(); + } + ]); + }); }); it('watches a file in code-splitting mode', () => { - return sander - .copydir('test/watch/samples/code-splitting') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: ['test/_tmp/input/main1.js', 'test/_tmp/input/main2.js'], - output: { - dir: 'test/_tmp/output', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/main1.js'), 21); - assert.strictEqual(run('../_tmp/output/main2.js'), 42); - atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/main1.js'), 22); - assert.strictEqual(run('../_tmp/output/main2.js'), 44); - } - ]); + return copy('test/watch/samples/code-splitting', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: ['test/_tmp/input/main1.js', 'test/_tmp/input/main2.js'], + output: { + dir: 'test/_tmp/output', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/main1.js'), 21); + assert.strictEqual(run('../_tmp/output/main2.js'), 42); + atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/main1.js'), 22); + assert.strictEqual(run('../_tmp/output/main2.js'), 44); + } + ]); + }); }); it('watches a file in code-splitting mode with an input object', () => { - return sander - .copydir('test/watch/samples/code-splitting') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: { - _main_1: 'test/_tmp/input/main1.js', - 'subfolder/_main_2': 'test/_tmp/input/main2.js' - }, - output: { - dir: 'test/_tmp/output', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/_main_1.js'), 21); - assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 42); - atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/_main_1.js'), 22); - assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 44); - } - ]); + return copy('test/watch/samples/code-splitting', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: { + _main_1: 'test/_tmp/input/main1.js', + 'subfolder/_main_2': 'test/_tmp/input/main2.js' + }, + output: { + dir: 'test/_tmp/output', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/_main_1.js'), 21); + assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 42); + atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 22;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/_main_1.js'), 22); + assert.strictEqual(run('../_tmp/output/subfolder/_main_2.js'), 44); + } + ]); + }); }); it('recovers from an error', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - atomicWriteFileSync('test/_tmp/input/main.js', 'export nope;'); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - } - ]); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); - }); - it('recovers from an error on initial build', () => { - return sander - .copydir('test/watch/samples/error') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + atomicWriteFileSync('test/_tmp/input/main.js', 'export nope;'); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - } - ]); + it('recovers from an error on initial build', () => { + return copy('test/watch/samples/error', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('recovers from a plugin error on initial build', () => { let count = 0; - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - transform() { - if (count++ === 0) { - this.error('The first run failed, try again.'); - } + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + transform() { + if (count++ === 0) { + this.error('The first run failed, try again.'); } - }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); } - ]); + }, + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('recovers from an error even when erroring entry was "renamed" (#38)', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - sander.unlinkSync('test/_tmp/input/main.js'); - atomicWriteFileSync('test/_tmp/input/main.js', 'export nope;'); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - sander.unlinkSync('test/_tmp/input/main.js'); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - } - ]); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + unlinkSync('test/_tmp/input/main.js'); + atomicWriteFileSync('test/_tmp/input/main.js', 'export nope;'); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + unlinkSync('test/_tmp/input/main.js'); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('recovers from an error even when erroring dependency was "renamed" (#38)', () => { - return sander - .copydir('test/watch/samples/dependency') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - sander.unlinkSync('test/_tmp/input/dep.js'); - atomicWriteFileSync('test/_tmp/input/dep.js', 'export nope;'); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - sander.unlinkSync('test/_tmp/input/dep.js'); - atomicWriteFileSync('test/_tmp/input/dep.js', 'export const value = 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 44); - } - ]); + return copy('test/watch/samples/dependency', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + unlinkSync('test/_tmp/input/dep.js'); + atomicWriteFileSync('test/_tmp/input/dep.js', 'export nope;'); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + unlinkSync('test/_tmp/input/dep.js'); + atomicWriteFileSync('test/_tmp/input/dep.js', 'export const value = 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 44); + } + ]); + }); }); it('handles closing the watcher during a build', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - load() { - watcher.close(); - } - }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - const events = []; - watcher.on('event', event => events.push(event.code)); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - () => { - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;'); - return wait(400).then(() => assert.deepStrictEqual(events, ['START', 'BUNDLE_START'])); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + load() { + watcher.close(); } - ]); + }, + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + const events = []; + watcher.on('event', event => events.push(event.code)); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + () => { + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;'); + return wait(400).then(() => assert.deepStrictEqual(events, ['START', 'BUNDLE_START'])); + } + ]); + }); }); it('handles closing the watcher during a build even if an error occurred', () => { - return sander - .copydir('test/watch/samples/error') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - load() { - watcher.close(); - } - }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - const events = []; - watcher.on('event', event => events.push(event.code)); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - () => { - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;'); - return wait(400).then(() => assert.deepStrictEqual(events, ['START', 'BUNDLE_START'])); + return copy('test/watch/samples/error', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + load() { + watcher.close(); } - ]); + }, + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); - }); + const events = []; + watcher.on('event', event => events.push(event.code)); - it('stops watching files that are no longer part of the graph', () => { - return sander - .copydir('test/watch/samples/dependency') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + () => { + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 44;'); + return wait(400).then(() => assert.deepStrictEqual(events, ['START', 'BUNDLE_START'])); + } + ]); + }); + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 42;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - let unexpectedEvent = false; - watcher.once('event', event => { - unexpectedEvent = event; - }); - atomicWriteFileSync('test/_tmp/input/dep.js', '= invalid'); - return wait(400).then(() => assert.strictEqual(unexpectedEvent, false)); - } - ]); + it('stops watching files that are no longer part of the graph', () => { + return copy('test/watch/samples/dependency', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 42;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + let unexpectedEvent = false; + watcher.once('event', event => { + unexpectedEvent = event; + }); + atomicWriteFileSync('test/_tmp/input/dep.js', '= invalid'); + return wait(400).then(() => assert.strictEqual(unexpectedEvent, false)); + } + ]); + }); }); it('refuses to watch the output file (#15)', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - atomicWriteFileSync('test/_tmp/input/main.js', `import '../output/bundle.js'`); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - event => { - assert.strictEqual(event.error.message, 'Cannot import the generated bundle'); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - } - ]); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + atomicWriteFileSync('test/_tmp/input/main.js', `import '../output/bundle.js'`); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + event => { + assert.strictEqual(event.error.message, 'Cannot import the generated bundle'); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('ignores files that are not specified in options.watch.include, if given', () => { - return sander - .copydir('test/watch/samples/ignored') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - watch: { - include: ['test/_tmp/input/+(main|foo).js'] - } - }); + return copy('test/watch/samples/ignored', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + watch: { + include: ['test/_tmp/input/+(main|foo).js'] + } + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-1', - bar: 'bar-1' - }); - atomicWriteFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-1', + bar: 'bar-1' + }); + atomicWriteFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); + let unexpectedEvent = false; + watcher.once('event', event => { + unexpectedEvent = event; + }); + atomicWriteFileSync('test/_tmp/input/bar.js', "export default 'bar-2';"); + return wait(400).then(() => { assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { foo: 'foo-2', bar: 'bar-1' }); - let unexpectedEvent = false; - watcher.once('event', event => { - unexpectedEvent = event; - }); - atomicWriteFileSync('test/_tmp/input/bar.js', "export default 'bar-2';"); - return wait(400).then(() => { - assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - assert.strictEqual(unexpectedEvent, false); - }); - } - ]); - }); + assert.strictEqual(unexpectedEvent, false); + }); + } + ]); + }); }); it('ignores files that are specified in options.watch.exclude, if given', () => { - return sander - .copydir('test/watch/samples/ignored') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - watch: { - exclude: ['test/_tmp/input/bar.js'] - } - }); + return copy('test/watch/samples/ignored', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + watch: { + exclude: ['test/_tmp/input/bar.js'] + } + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-1', - bar: 'bar-1' - }); - atomicWriteFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-1', + bar: 'bar-1' + }); + atomicWriteFileSync('test/_tmp/input/foo.js', `export default 'foo-2';`); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { + foo: 'foo-2', + bar: 'bar-1' + }); + let unexpectedEvent = false; + watcher.once('event', event => { + unexpectedEvent = event; + }); + atomicWriteFileSync('test/_tmp/input/bar.js', "export default 'bar-2';"); + return wait(400).then(() => { assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { foo: 'foo-2', bar: 'bar-1' }); - let unexpectedEvent = false; - watcher.once('event', event => { - unexpectedEvent = event; - }); - atomicWriteFileSync('test/_tmp/input/bar.js', "export default 'bar-2';"); - return wait(400).then(() => { - assert.deepStrictEqual(run('../_tmp/output/bundle.js'), { - foo: 'foo-2', - bar: 'bar-1' - }); - assert.strictEqual(unexpectedEvent, false); - }); - } - ]); - }); + assert.strictEqual(unexpectedEvent, false); + }); + } + ]); + }); }); it('only rebuilds the appropriate configs', () => { - return sander - .copydir('test/watch/samples/multiple') - .to('test/_tmp/input') + return copy('test/watch/samples/multiple', 'test/_tmp/input') .then(() => wait(100)) .then(() => { watcher = rollup.watch([ @@ -1019,9 +976,7 @@ describe('rollup.watch', () => { }); it('allows watching only some configs', () => { - return sander - .copydir('test/watch/samples/multiple') - .to('test/_tmp/input') + return copy('test/watch/samples/multiple', 'test/_tmp/input') .then(() => wait(100)) .then(() => { watcher = rollup.watch([ @@ -1050,14 +1005,8 @@ describe('rollup.watch', () => { 'BUNDLE_END', 'END', () => { - assert.strictEqual( - sander.existsSync(path.resolve(__dirname, '../_tmp/output/bundle1.js')), - false - ); - assert.strictEqual( - sander.existsSync(path.resolve(__dirname, '../_tmp/output/bundle2.js')), - true - ); + assert.strictEqual(existsSync(resolve(__dirname, '../_tmp/output/bundle1.js')), false); + assert.strictEqual(existsSync(resolve(__dirname, '../_tmp/output/bundle2.js')), true); assert.deepStrictEqual(run('../_tmp/output/bundle2.js'), 43); } ]); @@ -1065,258 +1014,246 @@ describe('rollup.watch', () => { }); it('respects output.globals', () => { - return sander - .copydir('test/watch/samples/globals') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'iife', - globals: { - jquery: 'jQuery' - } - }, - external: ['jquery'] - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const generated = sander.readFileSync('test/_tmp/output/bundle.js', { - encoding: 'utf-8' - }); - assert.ok(/jQuery/.test(generated)); + return copy('test/watch/samples/globals', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'iife', + globals: { + jquery: 'jQuery' } - ]); + }, + external: ['jquery'] }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + const generated = readFileSync('test/_tmp/output/bundle.js', { + encoding: 'utf-8' + }); + assert.ok(/jQuery/.test(generated)); + } + ]); + }); }); it('treats filenames literally, not as globs', () => { - return sander - .copydir('test/watch/samples/non-glob') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } - }); + return copy('test/watch/samples/non-glob', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + } + }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - atomicWriteFileSync('test/_tmp/input/[foo]/bar.js', `export const bar = 43;`); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 43); - } - ]); - }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + atomicWriteFileSync('test/_tmp/input/[foo]/bar.js', `export const bar = 43;`); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 43); + } + ]); + }); }); it('updates the right hashes on dependency changes', () => { let dynamicName; let staticName; let chunkName; - return sander - .copydir('test/watch/samples/hashing') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: ['test/_tmp/input/main-static.js', 'test/_tmp/input/main-dynamic.js'], - output: { - dir: 'test/_tmp/output', - format: 'cjs', - exports: 'auto', - entryFileNames: '[name].[hash].js', - chunkFileNames: '[name].[hash].js' - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - [dynamicName, staticName, chunkName] = sander.readdirSync('test/_tmp/output').sort(); - sander.rimrafSync('test/_tmp/output'); - - // this should only update the hash of that particular entry point - atomicWriteFileSync( - 'test/_tmp/input/main-static.js', - "import {value} from './shared';\nexport default 2 * value;" - ); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const [newDynamicName, newStaticName, newChunkName] = sander - .readdirSync('test/_tmp/output') - .sort(); - sander.rimrafSync('test/_tmp/output'); - assert.notEqual(newStaticName, staticName); - assert.strictEqual(newDynamicName, dynamicName); - assert.strictEqual(newChunkName, chunkName); - staticName = newStaticName; - - // this should update all hashes - atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 42;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - const [newDynamicName, newStaticName, newChunkName] = sander - .readdirSync('test/_tmp/output') - .sort(); - assert.notEqual(newStaticName, staticName); - assert.notEqual(newDynamicName, dynamicName); - assert.notEqual(newChunkName, chunkName); - } - ]); + return copy('test/watch/samples/hashing', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: ['test/_tmp/input/main-static.js', 'test/_tmp/input/main-dynamic.js'], + output: { + dir: 'test/_tmp/output', + format: 'cjs', + exports: 'auto', + entryFileNames: '[name].[hash].js', + chunkFileNames: '[name].[hash].js' + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + [dynamicName, staticName, chunkName] = readdirSync('test/_tmp/output').sort(); + removeSync('test/_tmp/output'); + + // this should only update the hash of that particular entry point + atomicWriteFileSync( + 'test/_tmp/input/main-static.js', + "import {value} from './shared';\nexport default 2 * value;" + ); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + const [newDynamicName, newStaticName, newChunkName] = + readdirSync('test/_tmp/output').sort(); + removeSync('test/_tmp/output'); + assert.notEqual(newStaticName, staticName); + assert.strictEqual(newDynamicName, dynamicName); + assert.strictEqual(newChunkName, chunkName); + staticName = newStaticName; + + // this should update all hashes + atomicWriteFileSync('test/_tmp/input/shared.js', 'export const value = 42;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + const [newDynamicName, newStaticName, newChunkName] = + readdirSync('test/_tmp/output').sort(); + assert.notEqual(newStaticName, staticName); + assert.notEqual(newDynamicName, dynamicName); + assert.notEqual(newChunkName, chunkName); + } + ]); + }); }); it('runs transforms again on previously erroring files that were changed back', () => { const brokenFiles = new Set(); const INITIAL_CONTENT = 'export default 42;'; - sander.writeFileSync('test/_tmp/input/main.js', INITIAL_CONTENT); - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - plugins: { - transform(code, id) { - if (code.includes('broken')) { - brokenFiles.add(id); - throw new Error('Broken in transform'); - } else { - brokenFiles.delete(id); + promises.writeFile('test/_tmp/input/main.js', INITIAL_CONTENT).then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + plugins: { + transform(code, id) { + if (code.includes('broken')) { + brokenFiles.add(id); + throw new Error('Broken in transform'); + } else { + brokenFiles.delete(id); + } + }, + generateBundle() { + if (brokenFiles.size > 0) { + throw new Error('Broken in generate'); + } } }, - generateBundle() { - if (brokenFiles.size > 0) { - throw new Error('Broken in generate'); - } + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' } - }, - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - } + }); + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default "broken";'); + }, + 'START', + 'BUNDLE_START', + 'ERROR', + () => { + atomicWriteFileSync('test/_tmp/input/main.js', INITIAL_CONTENT); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + } + ]); }); - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default "broken";'); - }, - 'START', - 'BUNDLE_START', - 'ERROR', - () => { - atomicWriteFileSync('test/_tmp/input/main.js', INITIAL_CONTENT); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - } - ]); }); it('skips filesystem writes when configured', () => { let watchChangeCnt = 0; - return sander - .copydir('test/watch/samples/skip-writes') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - watch: { - skipWrite: true - }, - plugins: { - watchChange(id) { - watchChangeCnt++; - assert.strictEqual(id, path.resolve('test/_tmp/input/main.js')); - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - watchChangeCnt = 0; - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - assert.strictEqual(watchChangeCnt, 1); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - assert.strictEqual(watchChangeCnt, 2); - atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - // 'END', - evt => { - assert.strictEqual(sander.existsSync('../_tmp/output/bundle.js'), false); - assert.strictEqual(watchChangeCnt, 3); - // still aware of its output destination - assert.strictEqual(evt.output[0], path.resolve('test/_tmp/output/bundle.js')); + return copy('test/watch/samples/skip-writes', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + watch: { + skipWrite: true + }, + plugins: { + watchChange(id) { + watchChangeCnt++; + assert.strictEqual(id, resolve('test/_tmp/input/main.js')); } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + watchChangeCnt = 0; + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + assert.strictEqual(watchChangeCnt, 1); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + assert.strictEqual(watchChangeCnt, 2); + atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + // 'END', + evt => { + assert.strictEqual(existsSync('../_tmp/output/bundle.js'), false); + assert.strictEqual(watchChangeCnt, 3); + // still aware of its output destination + assert.strictEqual(evt.output[0], resolve('test/_tmp/output/bundle.js')); + } + ]); + }); }); it('rebuilds immediately by default', async () => { - await sander.copydir('test/watch/samples/basic').to('test/_tmp/input'); + await copy('test/watch/samples/basic', 'test/_tmp/input'); + await wait(100); watcher = rollup.watch({ input: 'test/_tmp/input/main.js', output: { @@ -1337,7 +1274,7 @@ describe('rollup.watch', () => { () => { assert.strictEqual(run('../_tmp/output/bundle.js'), 42); atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - startTime = process.hrtime(); + startTime = hrtime(); }, 'START', 'BUNDLE_START', @@ -1354,7 +1291,7 @@ describe('rollup.watch', () => { }); it('observes configured build delays', async () => { - await sander.copydir('test/watch/samples/basic').to('test/_tmp/input'); + await copy('test/watch/samples/basic', 'test/_tmp/input'); watcher = rollup.watch( [ { @@ -1411,7 +1348,7 @@ describe('rollup.watch', () => { () => { assert.strictEqual(run('../_tmp/output/bundle.js'), 42); atomicWriteFileSync('test/_tmp/input/main.js', 'export default 43;'); - startTime = process.hrtime(); + startTime = hrtime(); }, 'START', 'BUNDLE_START', @@ -1434,362 +1371,336 @@ describe('rollup.watch', () => { describe('addWatchFile', () => { it('supports adding additional watch files in plugin hooks', () => { const watchChangeIds = new Set(); - const buildStartFile = path.resolve('test/_tmp/input/buildStart'); - const loadFile = path.resolve('test/_tmp/input/load'); - const resolveIdFile = path.resolve('test/_tmp/input/resolveId'); - const transformFile = path.resolve('test/_tmp/input/transform'); + const buildStartFile = resolve('test/_tmp/input/buildStart'); + const loadFile = resolve('test/_tmp/input/load'); + const resolveIdFile = resolve('test/_tmp/input/resolveId'); + const transformFile = resolve('test/_tmp/input/transform'); const watchFiles = [buildStartFile, loadFile, resolveIdFile, transformFile]; - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - for (const file of watchFiles) sander.writeFileSync(file, 'initial'); - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + for (const file of watchFiles) writeFileSync(file, 'initial'); + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + buildStart() { + this.addWatchFile(buildStartFile); }, - plugins: { - buildStart() { - this.addWatchFile(buildStartFile); - }, - load() { - this.addWatchFile(loadFile); - }, - resolveId() { - this.addWatchFile(resolveIdFile); - }, - transform() { - this.addWatchFile(transformFile); - }, - watchChange(id) { - watchChangeIds.add(id); - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - // sometimes the watcher is triggered during the initial run - watchChangeIds.clear(); - for (const file of watchFiles) sander.writeFileSync(file, 'changed'); + load() { + this.addWatchFile(loadFile); }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 42); - assert.deepStrictEqual([...watchChangeIds].sort(), watchFiles.sort()); + resolveId() { + this.addWatchFile(resolveIdFile); + }, + transform() { + this.addWatchFile(transformFile); + }, + watchChange(id) { + watchChangeIds.add(id); } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + // sometimes the watcher is triggered during the initial run + watchChangeIds.clear(); + for (const file of watchFiles) writeFileSync(file, 'changed'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 42); + assert.deepStrictEqual([...watchChangeIds].sort(), watchFiles.sort()); + } + ]); + }); }); it('respects changed watched files in the load hook', () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); - return sander - .copydir('test/watch/samples/watch-files') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - plugins: { - load() { - this.addWatchFile(WATCHED_ID); - return `export default "${sander.readFileSync(WATCHED_ID).toString().trim()}"`; - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); - atomicWriteFileSync(WATCHED_ID, 'next'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + const WATCHED_ID = resolve('test/_tmp/input/watched'); + return copy('test/watch/samples/watch-files', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + load() { + this.addWatchFile(WATCHED_ID); + return `export default "${readFileSync(WATCHED_ID).toString().trim()}"`; } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); + atomicWriteFileSync(WATCHED_ID, 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + } + ]); + }); }); it('respects changed watched files in the transform hook and removes them if they are no longer watched', () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); + const WATCHED_ID = resolve('test/_tmp/input/watched'); let addWatchFile = true; - return sander - .copydir('test/watch/samples/watch-files') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' + return copy('test/watch/samples/watch-files', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + resolveId(id) { + if (id === 'dep') { + return id; + } }, - plugins: { - resolveId(id) { - if (id === 'dep') { - return id; - } - }, - load(id) { - if (id === 'dep') { - return `throw new Error('This should not be executed);`; - } - }, - transform(code, id) { - if (id.endsWith('main.js')) { - return `export { value as default } from 'dep';`; - } else { - if (addWatchFile) { - this.addWatchFile(WATCHED_ID); - } - return `export const value = "${sander - .readFileSync(WATCHED_ID) - .toString() - .trim()}"`; - } + load(id) { + if (id === 'dep') { + return `throw new Error('This should not be executed);`; } - } - }); - const events = []; - watcher.on('event', event => events.push(event.code)); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); - addWatchFile = false; - atomicWriteFileSync(WATCHED_ID, 'next'); }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); - atomicWriteFileSync(WATCHED_ID, 'other'); - events.length = 0; - return wait(400).then(() => assert.deepStrictEqual(events, [])); + transform(code, id) { + if (id.endsWith('main.js')) { + return `export { value as default } from 'dep';`; + } else { + if (addWatchFile) { + this.addWatchFile(WATCHED_ID); + } + return `export const value = "${readFileSync(WATCHED_ID).toString().trim()}"`; + } } - ]); + } }); + const events = []; + watcher.on('event', event => events.push(event.code)); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'initial'); + addWatchFile = false; + atomicWriteFileSync(WATCHED_ID, 'next'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), 'next'); + atomicWriteFileSync(WATCHED_ID, 'other'); + events.length = 0; + return wait(400).then(() => assert.deepStrictEqual(events, [])); + } + ]); + }); }); it('respects changed watched modules that are already part of the graph in the transform hook', () => { - return sander - .copydir('test/watch/samples/dependencies') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - plugins: { - transform(code, id) { - if (id.endsWith('dep1.js')) { - this.addWatchFile(path.resolve('test/_tmp/input/dep2.js')); - const text = sander.readFileSync('test/_tmp/input/dep2.js').toString().trim(); - return `export default ${JSON.stringify(text)}`; - } + return copy('test/watch/samples/dependencies', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + transform(code, id) { + if (id.endsWith('dep1.js')) { + this.addWatchFile(resolve('test/_tmp/input/dep2.js')); + const text = readFileSync('test/_tmp/input/dep2.js').toString().trim(); + return `export default ${JSON.stringify(text)}`; } } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual( - run('../_tmp/output/bundle.js'), - `dep1: "export default 'dep2';", dep2: "dep2"` - ); - atomicWriteFileSync('test/_tmp/input/dep2.js', 'export default "next";'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual( - run('../_tmp/output/bundle.js'), - `dep1: "export default "next";", dep2: "next"` - ); - } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual( + run('../_tmp/output/bundle.js'), + `dep1: "export default 'dep2';", dep2: "dep2"` + ); + atomicWriteFileSync('test/_tmp/input/dep2.js', 'export default "next";'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual( + run('../_tmp/output/bundle.js'), + `dep1: "export default "next";", dep2: "next"` + ); + } + ]); + }); }); it('respects changed watched directories in the transform hook', () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); - return sander - .copydir('test/watch/samples/watch-files') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - plugins: { - transform() { - this.addWatchFile('test/_tmp/input'); - return `export default ${sander.existsSync(WATCHED_ID)}`; - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), true); - sander.unlinkSync(WATCHED_ID); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), false); - watcher.close(); + const WATCHED_ID = resolve('test/_tmp/input/watched'); + return copy('test/watch/samples/watch-files', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input'); + return `export default ${existsSync(WATCHED_ID)}`; } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), true); + unlinkSync(WATCHED_ID); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), false); + watcher.close(); + } + ]); + }); }); it('respects initially missing added watched files', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - plugins: { - transform() { - this.addWatchFile('test/_tmp/input/dep'); - return `export default ${sander.existsSync('test/_tmp/input/dep')}`; - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), false); - atomicWriteFileSync('test/_tmp/input/dep', ''); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), true); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input/dep'); + return `export default ${existsSync('test/_tmp/input/dep')}`; } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), false); + atomicWriteFileSync('test/_tmp/input/dep', ''); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), true); + } + ]); + }); }); it('respects unlinked and re-added watched files', () => { - return sander - .copydir('test/watch/samples/basic') - .to('test/_tmp/input') - .then(() => { - sander.writeFileSync('test/_tmp/input/dep', ''); - watcher = rollup.watch({ - input: 'test/_tmp/input/main.js', - output: { - file: 'test/_tmp/output/bundle.js', - format: 'cjs', - exports: 'auto' - }, - plugins: { - transform() { - this.addWatchFile('test/_tmp/input/dep'); - return `export default ${sander.existsSync('test/_tmp/input/dep')}`; - } - } - }); - - return sequence(watcher, [ - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), true); - sander.unlinkSync('test/_tmp/input/dep'); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), false); - atomicWriteFileSync('test/_tmp/input/dep', ''); - }, - 'START', - 'BUNDLE_START', - 'BUNDLE_END', - 'END', - () => { - assert.strictEqual(run('../_tmp/output/bundle.js'), true); + return copy('test/watch/samples/basic', 'test/_tmp/input').then(() => { + writeFileSync('test/_tmp/input/dep', ''); + watcher = rollup.watch({ + input: 'test/_tmp/input/main.js', + output: { + file: 'test/_tmp/output/bundle.js', + format: 'cjs', + exports: 'auto' + }, + plugins: { + transform() { + this.addWatchFile('test/_tmp/input/dep'); + return `export default ${existsSync('test/_tmp/input/dep')}`; } - ]); + } }); + + return sequence(watcher, [ + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), true); + unlinkSync('test/_tmp/input/dep'); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), false); + atomicWriteFileSync('test/_tmp/input/dep', ''); + }, + 'START', + 'BUNDLE_START', + 'BUNDLE_END', + 'END', + () => { + assert.strictEqual(run('../_tmp/output/bundle.js'), true); + } + ]); + }); }); it('does not rerun the transform hook if a non-watched change triggered the re-run', () => { - const WATCHED_ID = path.resolve('test/_tmp/input/watched'); + const WATCHED_ID = resolve('test/_tmp/input/watched'); let transformRuns = 0; - return sander - .copydir('test/watch/samples/watch-files') - .to('test/_tmp/input') + return copy('test/watch/samples/watch-files', 'test/_tmp/input') .then(() => wait(100)) .then(() => { - sander.writeFileSync('test/_tmp/input/alsoWatched', 'initial'); + writeFileSync('test/_tmp/input/alsoWatched', 'initial'); watcher = rollup.watch({ input: 'test/_tmp/input/main.js', output: { @@ -1804,7 +1715,7 @@ describe('rollup.watch', () => { transform() { transformRuns++; this.addWatchFile(WATCHED_ID); - return `export default "${sander.readFileSync(WATCHED_ID).toString().trim()}"`; + return `export default "${readFileSync(WATCHED_ID).toString().trim()}"`; } } });