typescript - No se puede usar la declaración import fuera de un módulo incluso en ESM
Así que tengo una aplicación que depende de una biblioteca que hemos publicado en NPM. Al importar esta biblioteca en una aplicación NextJs recién creada, lanza lo siguiente.
error - /Users/apple/testing-build/node_modules/foobar/dist/some-library.es.js:1
import t, { useState as M } from "react";
^^^^^^
SyntaxError: Cannot use import statement outside a module
at Object.compileFunction (node:vm:360:18)
at wrapSafe (node:internal/modules/cjs/loader:1094:15)
at Module._compile (node:internal/modules/cjs/loader:1129:27)
...
Estamos usando Node 18.13.0.
Ahora ya he intentado muchas cosas, cambié el tipo de mi aplicación a módulo. Cambié el tipo de la biblioteca a módulo e hice el mismo intento con CJS en ambos extremos.
La propia biblioteca está escrita en TypeScript y usa ESM y se compila a TypeScript. Aquí está el tsconfig.json de la biblioteca:
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"sourceMap": true,
"baseUrl": ".",
},
"include": ["./src/**/*.*"],
"references": [{ "path": "./tsconfig.node.json" }]
}
¡Y aquí está lo que tenemos de nuestro lado donde se está utilizando el paquete en sí!
{
"compilerOptions": {
"target": "es5", // Also tried with `ESNext`
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
Intenté ver el código dist que la biblioteca estaba generando, era el siguiente (Se eliminó la parte del medio).
// Beginning parts imports the libraries required
import t, { useState as D } from "react";
import l, { css as r } from "styled-components";
import { generateMedia as R } from "styled-media-query";
// ....
// The Ending part exports the components
export {
ot as Accordian,
Le as Badge,
K as Button,
nt as Card,
lt as CollapsibleText,
ct as Header,
at as If
};
Como puedes ver, la propia biblioteca se exporta en modo ESM. Y quien la usa la importa en modo ESM. ¡Cualquier ayuda aquí será muy apreciada! Gracias.
EDITAR: Estamos usando Babel y Vite para transpilar el paquete, aquí están las configuraciones.
import { resolve } from 'node:path';
import { defineConfig, loadEnv, splitVendorChunkPlugin } from 'vite';
import { externalizeDeps } from 'vite-plugin-externalize-deps';
import nodeResolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import dts from 'vite-plugin-dts';
import react from '@vitejs/plugin-react';
import * as pkg from './package.json';
/**
* Verify if the envs sent by the user were correct.
* Throws if not valid!
*
* @param {string} mode Default mode exposed by Vite
*/
function verifyEnv(mode: string): boolean {
switch (mode) {
// Fallthrough and return the name env
case 'test':
case 'local':
case 'production':
case 'dev': {
return true;
}
case 'development': {
throw new Error(
'Seems like you provided mode as `development` means you probably wanted to use `dev` which is another script. Prefer our script over `vite dev`.'
);
}
default: {
throw new Error(
`Either you provided an incorrect environment or you didn't send anything. Env received was ${process.env.NODE_ENV}.`
);
}
}
}
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
verifyEnv(mode);
const appEnv = loadEnv(mode, `${process.cwd()}/.env.${mode}`);
return {
/**
* Build options for Vite, these also contain information about
* rollup that vite uses under the hood.
*/
build: {
outDir: 'dist',
/**
* These are options passed as they are to rollup under the hood
*/
rollupOptions: {
input: './src/index.ts',
/**
* This is experimental with Rollup and enables optimizations on
* the rollup treeshaking algorithm. It supports `smallest`, `recommended`,
* and `safest`. We will try with smallest if it breaks something we
* should go with `recommended`.
*
* @see https://rollupjs.org/guide/en/#treeshake
*/
treeshake: 'smallest',
/**
* To import libraries that are compiled we need to make sure that we
* are able to support both CJS and module syntax.
* The NodeResolve and Commonjs help with that.
*/
plugins: [nodeResolve({ mainFields: ['module', 'main'], browser: true }), commonjs()],
output: {
/**
* We need to explicitly mention our dependencies and peerDependencies.
* This will let rollup know that these dependencies are the ones that we
* are including in our code.
*
* The `pkg` points to the `package.json` file and scans whatever we've mentioned
* in `dependencies` and `peerDependencies`. The problem here is that we require
* `react` and other dependencies to run this properly on development and storybooks
* but in the final build this is not required.
*
* As of right now we don't have any `peerDependencies` however at the time we include
* them we need to add `...pkg.peerDependencies` here too!
*/
globals: {
...pkg.dependencies
}
}
},
/**
* This will keep in note that the files that you export in your build
* will be compatible with both the `require` and `import` statement.
*
* @example
* The following two files will be generated!
*
* - foo-components.es.js
* - foo-components.umd.js
*
* This allows the following:
* ```js
* // Automatically references ES build
* import { Button } from 'foo-components';
* ```
*
* @see https://vitejs.dev/guide/build.html#library-mode
*/
lib: {
entry: resolve(process.cwd(), 'src/index.ts'),
name: 'Foo Components',
formats: ['es', 'umd'],
fileName: (format) => `foo-components.${format}.js`
}
},
/**
* We use aliases in our project that is natively mapped by TypeScript
* However for the transpilation to happen those aliases MUST also be
* also be defined for Vite.
*/
resolve: {
alias: [
{ find: '@src', replacement: resolve(__dirname, './src') },
{ find: '@utils', replacement: resolve(__dirname, './src/utils') },
{ find: '@themes', replacement: resolve(__dirname, './src/themes') },
{ find: '@components', replacement: resolve(__dirname, './src/components') },
{ find: '@dlsCss', replacement: resolve(__dirname, './src/design/dls.min.css') }
]
},
/**
* These are plugins that can be plugged into Vite to extend support
*/
plugins: [
/**
* Plugin to support React
*/
react({
// Exclude storybook stories
exclude: /\.stories\.(t|j)sx?$/,
// Support both TS and TSX files
include: ['./src/**/*.tsx', './src/**/*.ts'],
// Use babel.config.js
babel: { configFile: true }
}),
/**
* This will automatically generate a `d.ts` file for you.
* This is helpful for people using JavsScript
*/
dts({
include: ['./src/components']
}),
/**
* This will handle chunking if you introduce any vendor.
*/
splitVendorChunkPlugin(),
/**
* The dependencies that we are importing in our project are not
* to be packaged since they will be listed as peerDepencies and
* should be marked external.
*/
externalizeDeps()
],
/**
* Define enviornmental variables for your project
*/
define: {
__APP_ENV__: appEnv
}
};
});
Y aquí está la configuración de Babel
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 0.25%, not dead']
},
modules: false,
corejs: '3.6.5',
useBuiltIns: 'entry'
}
],
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-syntax-optional-chaining',
'styled-components',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-syntax-dynamic-import'
],
env: {
production: {
only: ['app'],
plugins: [
'transform-react-remove-prop-types',
'@babel/plugin-transform-react-inline-elements',
'@babel/plugin-transform-react-constant-elements'
]
},
test: {
plugins: [
'@babel/plugin-transform-modules-commonjs',
'dynamic-import-node',
[
'auto-import',
{
declarations: [{ default: 'React', path: 'react' }]
}
]
]
}
}
};