Is it possible to import ES Modules from CommonJS dynamically without having to change the file extension to mjs and if possible using old Node versions (older than V13)? I'm creating a CLI library which will dynamically import files from users project to auto-generate some code based on those files.
// bin.ts
// This file will be transpiled to CommonJS
const loadResourceFile = async (url: string) => {
const resource = await import(url);
return resource.default;
}
...
// rollup.config.js
import typescript from 'rollup-plugin-typescript2';
import pkg from './package.json';
const commonOutputOptions = {
banner: '#!/usr/bin/env node',
preferConst: true,
sourcemap: true,
};
export default {
input: 'src/bin.ts',
output: [
{
...commonOutputOptions,
file: pkg.main,
format: 'cjs',
},
{
...commonOutputOptions,
file: pkg.module,
format: 'esm',
},
],
external: [...Object.keys(pkg.dependencies || {})],
plugins: [typescript()],
inlineDynamicImports: true,
};
// resource.js
// This file will be a ES module
import a from './a';
export default {
a,
b: 'y',
}
Thank you in advance!
It is possible, with the use of vm (Specifically this) and fs although I would suggest not going this route since it quickly grows into an unmaintainable mess if you aren't careful.
Since your aim is to also support older nodejs versions, I would suggest you make two separate bundles, this way you do not mix CommonJS and ES modules.
Related
I found a plugin rollup-plugin-multi-input which fixes the problem of not being able to specifiy a glob to the test rollup config. For the unt tests, the entry point is not a single entity from which an import graph can be derived. It is just a collection of source files containing tests, which doesnt fit input requirement of rollup.
However, my attempt at trying to use it was fruitless:
rollup-config.tests.mjs:
import multi from 'rollup-plugin-multi-input';
const testConfig = {
input: ['test/**/*.spec.ts'],
external: ["chai", "mocha", "dirty-chai"],
output: {
format: "es",
file: `dist/${name}-bundle.test.js`,
plugins: [],
sourcemap: true
},
plugins: [
multi(),
resolve(),
commonjs(),
typescript({
tsconfig: "./tsconfig.test.json"
})
],
}
just resulted in this error:
[!] TypeError: multi is not a function
TypeError: multi is not a function
Looking at the exported code from the plugin, I can see that the default export is a function:
var _default = function(param) {
}
exports.default = _default;
So I don't know why this doesnt work.
I since discovered that there is another plugin that does a similar thing: #rollup/plugin-multi-entry:
import entry from "rollup-plugin-multi-entry";
plugins: [
entry(),
resolve(),
commonjs(),
typescript({
tsconfig: "./tsconfig.test.json"
})
],
so configured and invoked in exactly the same way, but now it works in the way that I wanted it to; the test bundle is created and mocha indeed sees all the tests and executes them successfully.
So let's take a look at that export and see if there is a difference in what is exported:
Well the first thing to notice is that its dist folder contains a .mjs file and a .js file. Since we're importing from an ESM package ("type": "module" in package.json), I guess we're using the default export from the .mjs file:
function multiEntry() {
...
}
export default multiEntry;
With rollup-plugin-multi-input, I even tried using the createRequire from "module":
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const multi = require('rollup-plugin-multi-input');
but that failed for the same reason.
So whats the problem here? Why does default import from rollup-plugin-multi-input not work?
So, I'm porting an old 2017 codebase from Webpack to Rollup for performance and code size reasons, also because of the old dependencies that the codebase used.
Unfortunately, The new Rollup version has a problem that I couldn't figure out a solution for. It doesn't seem to export some classes (In this case Engine and BackgroundLayer), but the Webpack unaltered version does. Is there a reason for this?
The Error in question:
Uncaught ReferenceError: Engine is not defined
Here's my rollup.config.js
import arraybuffer from '#wemap/rollup-plugin-arraybuffer';
import { babel } from "#rollup/plugin-babel";
import commonjs from "#rollup/plugin-commonjs";
import pkg from './package.json';
import resolve from "#rollup/plugin-node-resolve";
// import { terser } from "rollup-plugin-terser";
export default {
input: "src/index.js",
output: {
name: "index",
file: `dist/${pkg.name}.js`,
format: "umd",
},
external: ['ms'],
plugins: [
arraybuffer({ include: '**/*.dat' }), // so Rollup can import .dat files
resolve(), // so Rollup can find `ms`
commonjs(), // so Rollup can convert `ms` to an ES module
// terser(), // minifying
// babel configuration
babel({ exclude: 'node_modules/**', babelHelpers: "runtime", skipPreflightCheck: true }),
]
}
If anybody requires the full codebase, here are the two versions:
Webpack Code: https://github.com/kdex/earthbound-battle-backgrounds
Rollup Code: https://github.com/IamRifki/earthbound-battle-backgrounds-rollup
Figured it out, I had to call my bundle.js inside a module:
index.html
<script type="module">
import { BackgroundLayer, Engine } from "./bundle.js";
const engine = new Engine([new BackgroundLayer(153), new BackgroundLayer(298)]);
engine.animate();
</script>
When I use only const Example1 = require('./example1.js) statement then code inside example1.js file is getting included in the bundle. And if I use only import Example2 from './example2.js' then also code inside example2.js is getting included in the bundle. But if I use both the statements only import is working and require is not working.
I am using rollup for bundling.
My rollup configuration looks like this
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from 'rollup-plugin-node-resolve'
import url from 'rollup-plugin-url'
import svg from 'rollup-plugin-svg'
import json from 'rollup-plugin-json';
import { terser } from 'rollup-plugin-terser'
export default {
input: 'src/sdk/test.js',
output: [
{
file: "src/sdk/sdk.js",
format: 'cjs'
},
{
file: "src/sdk/sdk.es.js",
format: 'es'
},
{
file: "src/sdk/sdk.iife.js",
format: 'iife'
}
],
plugins: [
resolve({
browser: true,
}),
commonjs(),
external(),
postcss({
modules: true
}),
url({
limit: 100 * 1024,
emitFiles: false
}),
svg(),
babel({
exclude: 'node_modules/**',
"plugins": ["#babel/plugin-proposal-class-properties"]
}),
terser(),
json()
]
}
By default, rollup supports 'esm' modules as entry. If you like to include commonjs files in the project, you can include '#rollup/plugin-commonjs' into plugins field, it usually works.
Maybe the following config will help, I tried with very simple example:
commonjs({transformMixedEsModules:true})
transformMixedEsModules
Instructs the plugin whether or not to enable mixed module transformations. This is useful in scenarios with mixed ES and CommonJS modules. Set to true if it's known that require calls should be transformed, or false if the code contains env detection and the require should survive a transformation.
https://github.com/rollup/plugins/tree/master/packages/commonjs
I have two we components lets call then comp1 and comp2 and my project structure looks like this...
web-components
packages
comp1
package.json
comp2
package.json
lerna.json
rollup.config.json
package.json
Both of these import the Polymer material lib #material/mwc-icon-button. The problem arises when I build the projects. The material code is then injected into both outputs. I want to avoid this but I can't think of how to do it. I thought about using global variables but then the implementer has to know to install the material dependency and properly configure the global but this seems pretty onerous.
So the question is what is the proper way to do this without repeating code in the output?
Here is my full rollup config...
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import pug from 'rollup-plugin-pug';
import postcss from 'rollup-plugin-postcss';
const globals = {}
// const external = Object.keys(pkg.dependencies);
const plugins = [
resolve({
module: true,
main: true,
jsnext:"main",
browser: true
}),
postcss({
plugins: []
}),
commonjs(),
pug()
];
module.exports = {
plugins,
input: 'src/jrg-dropdown.mjs',
output: {
file: 'dist/index.mjs',
format: 'esm',
globals
}
};
I'm working on an Open Source D3/React component library and I'm trying to bundle the library using Rollup.js to offer code splitting, three shaking, etc.
The library is already published in GitHub and NPM and you can check a codesandbox using the library for a reference in case you want to try it.
Next I'm gonna try to highlight the different issues I'm experiencing with this bundle.
The library has already been tested using the code directly in a project and it works perfectly, so the problem is with the bundle and I assume that I'm doing something wrong with the Rollup.js configuration file in my project.
import { readdirSync } from 'fs';
import path from 'path';
import babel from 'rollup-plugin-babel';
import commonjs from 'rollup-plugin-commonjs';
import external from 'rollup-plugin-peer-deps-external';
import replace from 'rollup-plugin-replace';
import resolve from 'rollup-plugin-node-resolve';
import { terser } from 'rollup-plugin-terser';
const CODES = [
'THIS_IS_UNDEFINED',
'MISSING_GLOBAL_NAME',
'CIRCULAR_DEPENDENCY',
];
const getChunks = URI =>
readdirSync(path.resolve(URI))
.filter(x => x.includes('.js'))
.reduce((a, c) => ({ ...a, [c.replace('.js', '')]: `src/${c}` }), {});
const discardWarning = warning => {
if (CODES.includes(warning.code)) {
return;
}
console.error(warning);
};
const env = process.env.NODE_ENV;
const plugins = [
external(),
babel({
exclude: 'node_modules/**',
}),
resolve(),
replace({ 'process.env.NODE_ENV': JSON.stringify(env) }),
commonjs(),
env === 'production' && terser(),
];
export default [
{
onwarn: discardWarning,
input: 'src/index.js',
output: {
esModule: false,
file: 'umd/silky-charts.js',
format: 'umd',
name: 'silkyCharts',
},
plugins,
},
{
onwarn: discardWarning,
input: getChunks('src'),
output: [
{ dir: 'esm', format: 'esm', sourcemap: true },
{ dir: 'cjs', format: 'cjs', sourcemap: true },
],
plugins,
},
];
The Errors
When I try to use the library using the production bundle (which is the default) directly from the NPM package I got the following error coming from one of the chunks node_modules/silky-charts/esm/chunk-501b9e58.js:5833
TypeError: react__WEBPACK_IMPORTED_MODULE_0___default(...) is not a function
If I stead use the development bundle I get a different error:
Failed to compile
../silky-charts/esm/index.js
Module not found: Can't resolve 'react' in '/Users/davidg/Development/personal/silky-charts/esm'
This error force me to install React, ReactDOM, and styled-components as devDependencies in the project for the library have access to these projects code.
After installing the devDependencies the error I get is the next one:
Hooks can only be called inside the body of a function component.
I already filled an issue in the React project page and according to them this is not a React issue but maybe a Webpack's since is usual to find this error when you have to install React in both the project and the library and Webpack finds there is two instances of React, and I kind of agree since the error varies depending on bundle type or the way the importer project is run as you can see in the codesandbox.
I hope you can help me to spot the error in the Rollup configuration file and if you feel like doing a PR in the project event better 😀.