'Subject' is not exported by rxjs in rollup.js - javascript

I am trying to set up my project to use rollup, as part of an angular2 move to AOT compilation, however, I am getting the following issue.
Error: 'Subject' is not exported by node_modules\rxjs\Subject.js
This is my rollup.js file:
import rollup from 'rollup';
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify'
export default {
entry: 'client/main.js',
dest: 'public/assets/js/build.js',
sourceMap: false,
format: 'iife',
plugins: [
nodeResolve({jsnext: true, module: true}),
commonjs({
include: 'node_modules/rxjs/**',
include: 'node_modules/angular2-jwt/**',
}),
uglify()
]
}
Why is this happening, I have followed the angular2 cookbook guide?

You'll need to use the namedExports option with rollup-plugin-commonjs: https://github.com/rollup/rollup-plugin-commonjs#custom-named-exports.
Also, you may find it useful to include: 'node_modules/**' rather than individual packages, as otherwise any dependencies of your dependencies will bypass the plugin (in the config above, you have duplicate include properties – perhaps that's just a typo? If you need to pass multiple values, use an array).
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/rxjs/Subject.js': [ 'Subject' ]
}
})

I finally figured this out on my system.
The named export solution is wrong since rollup-plugin-commonjs will handle the exports in Subject.js just fine.
The problem for me was the "includes" option in rollup-plugin-commonjs.
There are two issues.
Number one: when specifying the includes in the options as "node_modules/rxjs/**" you have to be sure the full path resolves to where you expect it to.
Example:
I run my build command from "c:/foo/build" but my files are in "c:/my-source" then the include patterns might resolve to "c:/build/node_modules" which means when the commonjs plugin is checking if it should handle "node_modules/rxjs/" it will see that "c:/my-source/node_modules/rxjs/" does not match "c:/build/node_modules/rxjs/**" thus it will not convert the exports to ES6.
The second issue is case sensitivity. The include patterns are case sensitive. This tripped me up too.
Both of these issues can be confirmed by opening the "node_modules\rollup-plugin-commonjs\dist\rollup-plugin-commonjs.cjs.js" file and debugging the "transform" function.
If you modify the code (at the very beginning of the transform function) to be something like this
if (id.includes('rxjs\\Subject.js')) {
debugger;
}
and then run the code in a debugger, you can step through the code until it gets to the filter function. There you will see the filter function is skipping the "rxjs/Subject.js" file.
I almost guarantee this is the problem when the error occurs.

I have found that this can happen in combination with symlinks.

Related

Mock library with webpack

I'm attempting to replace a library for testing.
The code I want to change:
import foo from 'foo-lib'; // foo-lib is a library dependency in package.json
I want this to import src/mock-foo.js instead.
This is for end-to-end tests, so I can't use something like Jest mocks.
I have a development-only webpack.config. In that I've tried:
resolve: {
alias: {
'foo-lib': path.resolve(__dirname, 'src/mock-foo.js')
},
extensions: ['.js'], // need this for some other aliases (not shown)
},
I've also tried the older technique in the plugins array:
new webpack.NormalModuleReplacementPlugin(
/foo-lib/,
'mock-foo.js'
),
as well as several variations on these.
I don't get errors, but the original library loads every time.
Is this even possible? If so, what's the correct syntax?
I think I've found one solution based on Replacing/aliasing a file using Webpack .
Testing with the is-number library (just for a simple example), this works:
plugins: [
new webpack.NormalModuleReplacementPlugin(
/is-number/,
require.resolve('./src/mock.js')
),
],
As one of the commenters on the other post mentioned, it's not clear why the resolve.alias approach didn't work.

import css file into es6 returns string instead of object

TL;DR
I'm importing a css file into a typescript module, but the import resolves to a string instead of an object. Can anyone tell me why I don't get an object??
Example
// preview.ts
import test from './src/assets/test.theme.css';
// also tried this:
// import * as test from './src/assets/test.theme.css';
console.log('typeof test: ', typeof test);
console.log(test);
Console output
Detailed explanation
Actually, I'm trying to set up a Storybook for my Angular12 component library.
In order to provide various themes, I want to use the #etchteam/storybook-addon-css-variables-theme plugin, which in its documentation refers to the inline loader syntax of Webpack.
import myTheme from '!!style-loader?injectType=lazyStyleTag!css-loader!./assets/my-theme.css';
When applying this to my code my browser console started to complain
Error: myTheme.use is not a function
During my research I recognized that the imported stylesheet is not an evaluated javascript object, but instead it is provided as a string containing the sourcecode generated by the style-loader.
I also recognized, that this issue is not specific to the style-loader, but also occurs for all other loaders, e.g. css-loader, raw-loader, etc.
This issue is also not related to inline loader syntax, as it also shows up with loaders being defined in a minimalistic webpack config.
Environment:
Angular 12
Webpack 5
Reproduction
I have set up a GIT repo reproducing the issue.
The readme file explains the repro and the issue.
I think you have mistake in your Webpack config. You have nested rules property, instead you should have use:
{
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
],
}
https://webpack.js.org/loaders/css-loader/
I'm sorry, but I have to revert my last statement. My issue has NOT been resolved by #Akxe's comment.
Now my import statement (import * as test from '...') resolves to an object, but it's still not correct.
I have set up a GIT Repo to reproduce the issue. The readme.md file explains the repro and the issue.
It looks like Webpack is not executing/evaluating the return value of the loader.
Btw. this is not just the case with the css-loader. The result stays the same for raw-loader, sass-loader, style-loader, etc.
My final goal is to lazily load my theme files into a storybook.
I try to follow the documentation of the #etchteam/storybook-addon-css-variables-> theme.
Finally I got my issue solved!
Analysis
The main issue here is the webpack configuration generated by the #angular-devkit/build-angular package. I was able to analyze it by debugging a fresh angular12 application (you can check it out here).
By setting a break-point at /node_modules/#angular-devkit/build-angular/src/utils/webpack-browser-config.js, function: generateWebpackConfig(...), I could inspect the final webpackConfig object in the debugger.
The relevant rule looks like this:
The important part here is the rule setting the module type to asset/source, instructing webpack not to evaluate the loader's result.
Solution concept 1: inline loader
With the help of alexander-kait and his great hints at this issue,
I was able to find an inline-loader syntax that overrides webpack's module declaration:
import Test from 'test.css.webpack[javascript/auto]!=!!!style-loader?injectType=lazyStyleTag!css-loader!./test.css';
console.log(typeof Test); // output: object
console.log(Test); // output: Object { use: () => void, unuse: () => void }
Test.use(); // this should usually be called by a theme switcher...
I'm not really sure about the url pattern here, as it seems to be an undocumented feature, but I assume that it's something like <query-pattern>.webpack[<module-type>]!=!<loaders><query>.
However, since this is an undocumented feature, I was rather reluctant to use it.
Solution concept 2: webpackConfig customization
Since I'm in a storybook context, I decided to customize the webpack configuration according to the storybook documentation.
My solution requires to set up a naming convention (e.g. *.theme.css).
// .storybook/main.js
module.exports = {
webpackFinal: async (config) => {
// exclude *.theme.css from the *.css ruleset
config.module.rules.find(rule => '.css'.match(rule.test)).exclude = [ /\.(?:theme\.css)$/i ];
// add a rule for *.theme.css
config.module.rules.push({
test: /\.(?:theme\.css)$/i,
use: [
{ loader: 'style-loader', options: { injectType: 'lazyStyleTag' } },
'css-loader',
],
});
},
};
With these rules in place, I can now simply do the following:
// preview.js
import LightTheme from './light.theme.css';
import DarkTheme from './dark.theme.css';
setupThemeSwitcher(LightTheme, DarkTheme);
Please note that the setupThemeSwitcher function is just pseudocode merely there for the example. In reality I'm using the #etchteam/storybook-addon-css-variables-theme addon...
I had a very similar issue with storybook and this extension, except l’m loading .scss files.
I simply adapted solution 2 to suit my .scss case and it works like a charm.
I couldn’t make solution 1 to work, but as stated, it sounds hacky whereas solution 2 is cleaner in my opinion.
Thanks a lot for sharing this solution, I was struggling for hours.

How to remove comments when building TypeScript into JavaScripts using rollup

I am using rollup to build my TypeScript sources. I want to remove comments ONLY, without any minification, in order to hot update code when debugging.
I have tried rollup-plugin-terser, it can remove comments but it will also minify my code somehow, I cannot completely disable the minification.
How can I do that? Thanks!
Like #jujubes answered in the comments, the rollup-plugin-cleanup will do the task. I want to expand a bit.
Three things:
Add ts to extensions list, like extensions: ["js", "ts"] — otherwise sources won't be processed, even if transpiling step typescript() is before it — I originally came here investigating why rollup-plugin-cleanup won't work on TS files and it was just ts extension missing 🤦‍♂️
code coverage is important; on default settings, this plugin would remove istanbul statements like /* istanbul ignore else */ so it's good to exclude them, comments: "istanbul",
removing console.log is a separate challenge which is done with #rollup/plugin-strip and it goes in tandem to rollup-plugin-cleanup. In my case, depending is it a "dev" or "prod" Rollup build (controlled by a CLI flag --dev, as in rollup -c --dev), I remove console.log on prod builds only. But comments are removed on both dev and prod builds.
currently, I use:
import cleanup from "rollup-plugin-cleanup";
...
{
input: "src/main.ts",
output: ...,
external: ...,
plugins: [
...
cleanup({ comments: "istanbul", extensions: ["js", "ts"] }),
...
Here's an example of rollup-plugin-cleanup being used my Rollup config, here's my Rollup config generator (in monorepos, Rollup configs are hard to maintain by hand so I generate them). If you decide to wire up --dev CLI flag, the gotcha is you have to remove the flag from the commandLineArgs before script ends, otherwise Rollup will throw, see the original tip and it in action.
You should be able to achieve this too with just rollup-plugin-terser. It bases on terser so more information it's actually available on its README, here is the part related to minification. So in your case this part of rollup.config.js should looks like:
plugins: [
terser({
// remove all comments
format: {
comments: false
},
// prevent any compression
compress: false
}),
],
Keep in mind, that you can also enable part of configuration for production only. So having declared production const in your rollup.config.js you can do like that:
import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
export default {
plugins: [
production && terser({
// terser plugin config here
}),
],
};

'createConsumer' is not exported by node_modules/#rails/actioncable/app/assets/javascripts/action_cable.js

I'm trying to import createConsumer from actioncable in the following script. https://github.com/jonathan-s/sockpuppet/blob/stimulus/javascript/stimulus/consumer.js#L1
When I try to build it with rollup.js it all fails with the following error.
[!] Error: 'createConsumer' is not exported by
node_modules/#rails/actioncable/app/assets/javascripts/action_cable.js,
imported by javascript/stimulus/consumer.js
When I take a look at node_modules, it does look like action_cable.js is exporting createConsumer so I'm unsure of what is going wrong here.
What made the error go away was to use commonjs with named exports.
plugins: [
commonjs({
namedExports: {
"node_modules/#rails/actioncable/app/assets/javascripts/action_cable.js": ["createConsumer"]
}
}),
]

Rule.issuer in Webpack. What properties belong to it?

Webpack's Rule option presents two things (in a full, not shortcut syntax): resource and issuer.
In a Rule the properties test, include, exclude and resource are matched with the resource and the property issuer is matched with the issuer.
So, it is somewhat clear what properties are related to a resource:
{
resource: {
test: ...,
include: ...,
exclude: ...,
},
issuer: { ...boom?? }
}
But what properties are matched with the issuer? There is just nothing for issuer in their docs:
A Condition matched with the issuer. See details in Rule conditions.
And details do not explain issuer.
Why? They have created an option, but haven't decided on its properties yet?
They have created an option, but haven't decided on its properties yet?
The value of issuer is a Condition. The most common Condition is an object with test, include and/or exclude properties, which you have used for the resource. Everything you can use for resource, you can also use for issuer.
In fact, Rule.resource expects itself a Condition, excerpt from the docs:
Rule.resource
A Condition matched with the resource. You can either supply a Rule.resource option or use the shortcut options Rule.test, Rule.exclude, and Rule.include.
The only difference to the issuer is that there are shortcuts (Rule.test, Rule.exclude and Rule.include), because that's the majority of the use-cases. It roughly translates to:
resource: {
test: Rule.test,
exclude: Rule.exclude,
include: Rule.include,
}
And details do not explain issuer.
Clicking on See details in Rule conditions leads to a description, which even contains an example. Excerpt:
Rule Conditions
There are two input values for the conditions:
The resource: An absolute path to the file requested. It's already resolved according to the resolve rules.
The issuer: An absolute path to the file of the module which requested the resource. It's the location of the import.
Example: When we import "./style.css" from app.js, the resource is /path/to/style.css and the issuer is /path/to/app.js.
That is definitely an explanation, but maybe it isn't good enough, so I'll explain it in more details.
To illustrate the purpose of issuer I will use a contrived example, which could potentially be a use-case for it. Let's assume that you have some JavaScript code, which you would like to show to the user (the actual code) and at the same time you want to run that code in another part of the application. The code in question will be a simple greeting function.
greeter.js
export default function greet(name) {
console.log(`Hello ${name}!`);
}
If we want to show the source of greeter.js, we could read it from the file system, but because we'd like to run it in the browser, this is not an option. As we are using webpack, we can use the raw-loader to import the greeter.js file as a string instead of JavaScript. Assuming we have configured it, we can create a module that prints the source code.
printer.js
import greetSource from "./greeter";
export default function printSource() {
console.log(greetSource);
}
In our entry point we want to use the greeter and the printer at the same time.
index.js
import greet from "./greeter";
import printSource from "./printer";
greet("World");
printSource();
Now we have a problem, because we've configured raw-loader for greeter.js, therefore greet will be a string, not a function and that will cause a runtime error. What we want is to import greeter.js in index.js as a regular JavaScript file, but we want to import it as a string in printer.js. We could use an inline loader definition, but that would be rather tedious.
This is where issuer comes in. Whichever JavaScript file is imported from printer.js should be passed through the raw-loader, and from any other file we'd like to use babel-loader.
We will define two webpack rules. Both rules target only JavaScript files, so we test for the .js file ending, for every file that is imported, this it the resource. We would like to know which file imported it (where the import statement was), this is the issuer.
printer.js
// Resource: greeter.js, Issuer: printer.js
import greetSource from "./greeter";
index.js
// Resource: greeter.js, Issuer: index.js
import greet from "./greeter";
For the rules, it means that we want to exclude printer.js as an issuer from the babel-loader rule, and include only printer.js for the raw-loader rule.
module: {
rules: [
{
loader: "babel-loader",
resource: {
test: /\.js$/
},
issuer: {
exclude: /printer\.js$/
}
},
{
loader: "raw-loader",
resource: {
test: /\.js$/
},
issuer: {
include: /printer\.js$/
}
}
]
}
Note: It's not necessary to include the resource option for the raw-loader rule and if you leave it out, it would apply the raw-loader to everything that is being imported in printer.js, which may or may not be what you want (think of including CSS to style the output)
Bundling and running the above code will produce the following output:
Hello World!
export default function greet(name) {
console.log(`Hello ${name}!`);
}

Categories

Resources