diff --git a/docs/05-configuration.md b/docs/05-configuration.md index 8e2a4b019f..446e77b7ef 100644 --- a/docs/05-configuration.md +++ b/docs/05-configuration.md @@ -93,11 +93,15 @@ $ snowpack dev --no-bundle - **`alias`** | `{[mapFromPackageName: string]: string}` - Alias an installed package name. This applies to imports within your application and within your installed dependency graph. - Example: `"alias": {"react": "preact/compat", "react-dom": "preact/compat"}` +- **`namedExports`** | `string[]` + - Legacy Common.js (CJS) packages should only be imported by the default import (Example: `import reactTable from 'react-table'`) + - But, some packages use named exports in their documentation, which can cause confusion for users. (Example: `import {useTable} from 'react-table'`) + - You can enable "fake/synthetic" named exports for Common.js package by adding the package name under this configuration. + - Example: `"namedExports": ["react-table"]` - **`rollup`** - Snowpack uses Rollup internally to install your packages. This `rollup` config option gives you deeper control over the internal rollup configuration that we use. - **`rollup.plugins`** - Specify [Custom Rollup plugins](#installing-non-js-packages) if you are dealing with non-standard files. - **`rollup.dedupe`** - If needed, deduplicate multiple versions/copies of a packages to a single one. This helps prevent issues with some packages when multiple versions are installed from your node_modules tree. See [rollup-plugin-node-resolve](https://github.com/rollup/plugins/tree/master/packages/node-resolve#usage) for more documentation. - - **`rollup.namedExports`** - **DEPRECATED** Rollup has gotten good enough at Common.js<->ESM interop that it no longer needs this fallback, and will fail if you pass it. If you're currently using it, it should be safe to remove. #### Dev Options diff --git a/src/commands/install.ts b/src/commands/install.ts index 1da69db4ab..bc25f994e2 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -48,14 +48,16 @@ class ErrorWithHint extends Error { // import React from 'react'; // But, some large projects use named exports in their documentation: // import {useState} from 'react'; -// Note that this is not a problem for ESM packages, only CJS. +// +// We use "/index.js here to match the official package, but not any ESM aliase packages +// that the user may have installed instead (ex: react-esm). const CJS_PACKAGES_TO_AUTO_DETECT = [ 'react/index.js', 'react-dom/index.js', - 'react-table/index.js', 'react-is/index.js', 'prop-types/index.js', 'scheduler/index.js', + 'react-table', ]; const cwd = process.cwd(); @@ -335,7 +337,11 @@ export async function install( rollupPluginCommonjs({ extensions: ['.js', '.cjs'], // Default: [ '.js' ] }), - rollupPluginWrapInstallTargets(!!isTreeshake, CJS_PACKAGES_TO_AUTO_DETECT, installTargets), + rollupPluginWrapInstallTargets( + !!isTreeshake, + [...CJS_PACKAGES_TO_AUTO_DETECT, ...config.installOptions.namedExports], + installTargets, + ), rollupPluginDependencyStats((info) => (dependencyStats = info)), ...userDefinedRollup.plugins, // load user-defined plugins last rollupPluginCatchUnresolved(), diff --git a/src/config.ts b/src/config.ts index 2ffb2c27ad..8bdb642f3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -108,6 +108,7 @@ export interface SnowpackConfig { sourceMap?: boolean | 'inline'; externalPackage: string[]; alias: {[key: string]: string}; + namedExports: string[]; rollup: { plugins: RollupPlugin[]; // for simplicity, only Rollup plugins are supported for now dedupe?: string[]; @@ -136,6 +137,7 @@ const DEFAULT_CONFIG: Partial = { installTypes: false, env: {}, alias: {}, + namedExports: [], rollup: { plugins: [], dedupe: [], @@ -532,14 +534,14 @@ function validateConfigAgainstV1(rawConfig: any, cliFlags: any) { } if (rawConfig.namedExports) { handleDeprecatedConfigError( - '[Snowpack v1 -> v2] `namedExports` was removed in the latest version of Rollup, and should no longer be needed.', + '[Snowpack v1 -> v2] `rollup.namedExports` is no longer required. See also: installOptions.namedExports', ); } if (rawConfig.installOptions?.rollup?.namedExports) { delete rawConfig.installOptions.rollup.namedExports; console.error( chalk.yellow( - '[Snowpack v2.3.0] `namedExports` was removed in the latest version of Rollup, and is now safe to remove from your config.', + '[Snowpack v2.3.0] `rollup.namedExports` is no longer required. See also: installOptions.namedExports', ), ); } diff --git a/src/rollup-plugin-wrap-install-targets.ts b/src/rollup-plugin-wrap-install-targets.ts index 5ce5ac7b38..ff95a78c3b 100644 --- a/src/rollup-plugin-wrap-install-targets.ts +++ b/src/rollup-plugin-wrap-install-targets.ts @@ -33,7 +33,9 @@ export function rollupPluginWrapInstallTargets( const installTargetsByFile: {[loc: string]: InstallTarget[]} = {}; function isAutoDetect(normalizedFileLoc: string) { - return autoDetectPackageExports.some((p) => normalizedFileLoc.includes(`node_modules/${p}`)); + return autoDetectPackageExports.some((p) => + normalizedFileLoc.includes(`node_modules/${p}${p.endsWith('index.js') ? '' : '/'}`), + ); } return { name: 'snowpack:wrap-install-targets', diff --git a/test/integration/config-named-exports/expected-install/import-map.json b/test/integration/config-named-exports/expected-install/import-map.json new file mode 100644 index 0000000000..e923183023 --- /dev/null +++ b/test/integration/config-named-exports/expected-install/import-map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "mock-test-package": "./mock-test-package.js" + } +} \ No newline at end of file diff --git a/test/integration/config-named-exports/expected-install/mock-test-package.js b/test/integration/config-named-exports/expected-install/mock-test-package.js new file mode 100644 index 0000000000..404a3663bf --- /dev/null +++ b/test/integration/config-named-exports/expected-install/mock-test-package.js @@ -0,0 +1,8 @@ +var entrypoint = { + export1: 'foo', + export2: 'bar', +}; +var entrypoint_1 = entrypoint.export1; +var entrypoint_2 = entrypoint.export2; + +export { entrypoint_1 as export1, entrypoint_2 as export2 }; diff --git a/test/integration/config-named-exports/expected-output.txt b/test/integration/config-named-exports/expected-output.txt new file mode 100644 index 0000000000..9fbf137bcf --- /dev/null +++ b/test/integration/config-named-exports/expected-output.txt @@ -0,0 +1,4 @@ +- snowpack installing... +✔ snowpack install complete. + ⦿ web_modules/ size gzip brotli + └─ mock-test-package.js XXXX KB XXXX KB XXXX KB \ No newline at end of file diff --git a/test/integration/config-named-exports/node_modules/mock-test-package/entrypoint.js b/test/integration/config-named-exports/node_modules/mock-test-package/entrypoint.js new file mode 100644 index 0000000000..f6fddea3cc --- /dev/null +++ b/test/integration/config-named-exports/node_modules/mock-test-package/entrypoint.js @@ -0,0 +1,4 @@ +module.exports = { + export1: 'foo', + export2: 'bar', +}; \ No newline at end of file diff --git a/test/integration/config-named-exports/node_modules/mock-test-package/package.json b/test/integration/config-named-exports/node_modules/mock-test-package/package.json new file mode 100644 index 0000000000..ae7fc40035 --- /dev/null +++ b/test/integration/config-named-exports/node_modules/mock-test-package/package.json @@ -0,0 +1,5 @@ +{ + "name": "mock-test-package", + "version": "1.2.3", + "main": "entrypoint.js" +} diff --git a/test/integration/config-named-exports/package.json b/test/integration/config-named-exports/package.json new file mode 100644 index 0000000000..81b12ab61f --- /dev/null +++ b/test/integration/config-named-exports/package.json @@ -0,0 +1,15 @@ +{ + "description": "Handle installOptions.namedExports", + "scripts": { + "TEST": "node ../../../pkg/dist-node/index.bin.js" + }, + "snowpack": { + "install": ["mock-test-package"], + "installOptions": { + "namedExports": ["mock-test-package"] + } + }, + "dependencies": { + "mock-test-package": "^1.0.0" + } +}