Mock library with webpack - javascript

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.

Related

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.

Babel is trying to import a module that doesn't exist

My compiled Babel output tries to import a function/file that does not exist. Am I missing a configuration or step in my Mix/Babel/Webpack configuration that would output this file?
I am using Laravel Mix (5.0.4) with its default configurations.
I've recently used the Javascript await operator for the first time. It is causing an issue with Babel. When Babel processes await to make it backwards compatible, it adds import _regeneratorRuntime from "#babel/runtime/regenerator"; to the beginning of the Javascript file. However, babel/runtime/regenerator doesn't actually exist. This causes the Javascript to fail when the browser attempts to load it, producing the error Error resolving module specifier: #babel/runtime/regenerator.
I am beyond my understanding of how Mix, Babel, and Webpack work together. I do not know how to tell Mix/Babel/Webpack to produce file(s) that contain the necessary module(s), or if there's something else I need to be doing.
I've tried many solutions via googling, played with the configuration files, and hit my head against my desk a bunch of times. None of these worked. I'm not sure if I am even asking the right questions.
Debugging info:
webpack.mix.js looks like this:
const mix = require('laravel-mix');
// Use of mix.babel() is imperative as this is legacy code and cannot leverage mix.js()
mix.babel('public/js/helpers.js', 'public/js/processed/helpers.js')
.babel('public/js/main.js', 'public/js/processed/main.js')
.babel('public/js/stripe.js', 'public/js/processed/stripe.js');
The problematic Javascript looks like this:
function foo() {
const bar = document.getElementById('bar');
bar.addEventListener('click', async (event) => {
// ('async' is the part which causes the `import` to be added)
});
}
And when run through Babel, looks like this:
import _regeneratorRuntime from"#babel/runtime/regenerator";function asyncGeneratorStep(n,e,r,t,o,a,u){try{var c=n[a](u),i=c.value}catch(n){return void r(n)}c.done?e(i):Promise.resolve(i).then(t,o)}function _asyncToGenerator(n){return function(){var e=this,r=arguments;return new Promise(function(t,o){var a=n.apply(e,r);function u(n){asyncGeneratorStep(a,t,o,u,c,"next",n)}function c(n){asyncGeneratorStep(a,t,o,u,c,"throw",n)}u(void 0)})}}function foo(){document.getElementById("bar").addEventListener("click",function(){var n=_asyncToGenerator(_regeneratorRuntime.mark(function n(e){return _regeneratorRuntime.wrap(function(n){for(;;)switch(n.prev=n.next){case 0:case"end":return n.stop()}},n)}));return function(e){return n.apply(this,arguments)}}())}
When I dig into Mix's default Babel config, I think it's using this:
{
cacheDirectory: true,
presets: [
[
'#babel/preset-env',
{
modules: false,
forceAllTransforms: true
}
]
],
plugins: [
'#babel/plugin-syntax-dynamic-import',
'#babel/plugin-proposal-object-rest-spread',
[
'#babel/plugin-transform-runtime',
{
helpers: false
}
]
]
}
Use js mixin instead:
const mix = require('laravel-mix');
mix.js('public/js/helpers.js', 'public/js/processed/helpers.js')
.js('public/js/main.js', 'public/js/processed/main.js')
.js('public/js/stripe.js', 'public/js/processed/stripe.js');
I was not able to find a working solution in a reasonable time for the legacy project I was working on, so I just used a workaround and documented it. I tried various solutions, different libraries and compilers, upgrading existing libraries, better workarounds and so on. Because this was a legacy project, most changes/updates/tool swaps resulted in a cascade of changes needed (and it still didn't work in the end after making all of those changes).
Ultimately, all I did was take the offending bit of Javascript (it was only one small function) and moved it to its own Javascript that does not get processed by Babel.
// The code that gets processed fine is here:
mix.babel('public/js/stripe.js', 'public/js/processed/stripe.js')
// The code that doesn't get processed without error is here:
.copy('public/js/stripeUnminified.js', 'public/js/processed/stripeUnminified.js');
Considering the time I had invested, this workaround was an ok solution. Running the offending bit of Javascript through a compiler such as Babel just wasn't actually a super critical priority considering all of the headache it was causing and time it was taking. It was looking like I was going to have to rework and update a lot of the project just to fix this one little problem (and still a fix was not guaranteed).

How to import dynamically with webpack

I want to use FontAwesome's icons, but the whole package is too large and I have to select only the ones that I'm using
I'm using vue & webpack
Right now I have the standard:
import { library } from '#fortawesome/fontawesome-svg-core'
import { faGem as falFaGem, faDatabase as falFaDatabase } from '#fortawesome/pro-light-svg-icons'
import { faGem as farFaGem } from '#fortawesome/pro-regular-svg-icons'
import { faGem as fasFaGem } from '#fortawesome/pro-solid-svg-icons'
library.add(falFaGem, falFaDatabase, farFaGem, fasFaGem)
The thing is I have around 80 (for now) icons and some of them duplicates like faGem in the example, hence the "faGem as farFaGem"
I tried importing FAS from '#fortawesome/pro-regular-svg-icons' and making a foreach and adding to library only the icons that I need but webpack imports the whole package into the compiled code
Is there an easier, cleaner way to achieve this?
I believe that the title to your question is a bit wrong. What you want to achieve is reduce the size of the FontAwesomes npm package and that is something that can be achieved in different ways.
The most common way nowadays is using Treeshaking. Basically, your code will be '''analysed''' and a graph of dependencies will be generated, before giving you the "compiled" version of your code it will remove all those modules that were not used from FontAwesome.
FontAwesome can perform TreeShaking if your tool (webpack) allows it, sadly it seems that there are some problems with Webpack 4 but they offer some work arounds like setting the variable modules false in your config:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: './bundle.js'
},
module: {
rules: [{
test: /\.js$/,
include: path.resolve(__dirname, 'src'),
use: {
loader: 'babel-loader',
options: {
presets: [
['#babel/preset-env', { modules: false }],
],
plugins: ['#babel/plugin-proposal-class-properties'],
}
}
},
]
}
};
In the other hand I believe that what you want to achieve is quite difficult (you will still need to declare all the icons that you want to use from FontAwesome (to use a require(...) call dynamically is something that the tree shaking algorithm can not work around and it will import the whole npm package), and the same for the name conflicts.
If you want it to be a bit cleaner, maybe declare and add all this icons in a separated file of your project, but as far as I can tell, there is no a better way to achieve what you want.

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

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.

require('template.jade') in react-starter-kit

Can someone maybe explain me, how this build-time require works?
https://github.com/kriasoft/react-starter-kit/blob/feature/redux/src/server.js#L89
They are requiring a jade template, which package or configuration allows this, I seem unable to find it myself.
const template = require('./views/index.jade')
I think is much more elegant then:
import jade from 'jade'
const template = jade.compile('./views/index.jade')
As RGraham mentioned in his comment, the require call is being "intercepted" during webpack's compilation of the application bundle. This is done using "loaders" that define particular behaviour for imports of a particular type:
Loaders allow you to preprocess files as you require() or “load” them.
In this particular case, the loader that does this modification could be one of these (or another that I didn't find in my search):
https://github.com/bline/jade-html-loader
https://github.com/webpack/jade-loader
Edit: looking at the project's own webpack configuration we can see it is the second link above:
{
test: /\.jade$/,
loader: 'jade-loader',
}
jade-loader reads the content of the specified file, which make look something like this (Jade string):
h1 Hello, #{author}!
..and replaces that with a CommonJS JavaScript code similar to this (at compile time):
module.exports = function(data) {
return `<h1>Hello, ${data.name}</h1>`;
};

Categories

Resources