Unable to get `ReactComponent` export of #svgr/webpack - svg

I am trying to get the same import statements going for both a Vite and Webpack setup (used by Storybook) and I am failing to get it to work for Webpack as advertised.
According to the docs for #svgs/webpack, version 5.5, this should work:
// Does not work (undefined)
import {ReactComponent as UilPlus} from '#iconscout/unicons/svg/line/plus.svg'
But it does not. There is only the default export:
// Works
import UilPlus from '#iconscout/unicons/svg/line/plus.svg'
How can I fix this? I have even tried explicitly using the namedExport option, but it does not take effect.
Config:
use: [
{
loader: '#svgr/webpack',
options: { namedExport: 'ReactComponent' }
},
$ jq .version node_modules/\#svgr/webpack/package.json
"5.5.0"

After reading the source code for v5.5 of #svgr/webpack, #svgr/core and #svgr/babel-plugin-transform-svg-component and reading up on how loaders work, things finally made sense.
Turns out there was a crucial bit missing: the example showing use of ReactComponent has an extra loader added for the svg processing. I did not understand how a loader added later could affect something earlier. That was until I read the loader docs for Webpack 4 which explains how loaders are applied Right to Left.
Once that was out of the way, it was now clear that the svgr plugin somehow sees that the input it receives is from another loader and adjusts it behavior accordingly. That is exactly what happens in #svgr/webpack in line 32 to 41 and you can see the expected output in the snapshot test:
...
export default \\"data:image/svg+xml;base64,PD94bWwg..."
export { SvgIcon as ReactComponent };"```
The webpack module actually only does the detection and passes that info on to #svgr/core, whereas the core module is using babel-plugin-transform-svg-component to actually change the output based on the presence of a previous export.

As said in doc of #svgr/webpack, you should add url-loader or file-loader
https://github.com/gregberge/svgr/tree/main/packages/webpack
{
test: /\.svg$/,
use: ['#svgr/webpack', 'url-loader'],
}
then you can use it in your code
import starUrl, { ReactComponent as Star } from './star.svg'
const App = () => (
<div>
<img src={starUrl} alt="star" />
<Star />
</div>
)

Related

Vite: Cannot use import statement outside a module

I know little about bundler and I'm using vite to build project, I got a error when import some package to configure dev server :
SyntaxError: Cannot use import statement outside a module
So here is the thing:
import pinyin from 'pinyin/esm/pinyin-web.js'
export const somePlugin = {
name: 'someplugin',
configureServer(server) {
server.middlewares.use('/somepath', (req, res, next) => {
const foo = pinyin('foo')
next()
})
},
}
I don't use the normal way(import pinyin from 'pinyin') , because that need a package nodejieba which need to install unnecessary node-gyp, so I choose the web version that don't need nodejieba.
I've searched the error, some says add "type": "module" to package.json file. but it already exist in my package.json.
however, I make the change:
// import pinyin from 'pinyin/esm/pinyin-web.js'
import pinyin from 'pinyin/lib/pinyin-web.js'
and problem get solved,I was confused because I thought vite prefer ES module.
So,
1> what cause the problem above?
2> why should I import file with extensions ? eg: import pinyin from 'pinyin/lib/pinyin-web.js'
I have to add extensions .js or it will cause error. while in vite.config.ts I needn't add extensions.
3> I tried to add field optimizeDeps in vite.config.ts like this
export default defineConfig({
plugins: [vue(), somePlugin],
optimizeDeps: {
include: ['pinyin'],
},
})
but it seems to be useless, the offical doc says:
"During development, Vite's dev serves all code as native ESM. Therefore, Vite must convert dependencies that are shipped as CommonJS or UMD into ESM first."
did that work for the frontend part and package "pinyin" is for the dev server so whether add the
field optimizeDeps there is no difference.
codesandbox

How to import a node module inside an angular web worker?

I try to import a node module inside an Angular 8 web worker, but get an compile error 'Cannot find module'. Anyone know how to solve this?
I created a new worker inside my electron project with ng generate web-worker app, like described in the above mentioned ng documentation.
All works fine until i add some import like path or fs-extra e.g.:
/// <reference lib="webworker" />
import * as path from 'path';
addEventListener('message', ({ data }) => {
console.log(path.resolve('/'))
const response = `worker response to ${data}`;
postMessage(response);
});
This import works fine in any other ts component but inside the web worker i get a compile error with this message e.g.
Error: app/app.worker.ts:3:23 - error TS2307: Cannot find module 'path'.
How can i fix this? Maybe i need some additional parameter in the generated tsconfig.worker.json?
To reproduce the error, run:
$ git clone https://github.com/hoefling/stackoverflow-57774039
$ cd stackoverflow-57774039
$ yarn build
Or check out the project's build log on Travis.
Note:
1) I only found this as a similar problem, but the answer handles only custom modules.
2) I tested the same import with a minimal electron seed which uses web workers and it worked, but this example uses plain java script without angular.
1. TypeScript error
As you've noticed the first error is a TypeScript error. Looking at the tsconfig.worker.json I've found that it sets types to an empty array:
{
"compilerOptions": {
"types": [],
// ...
}
// ...
}
Specifying types turns off the automatic inclusion of #types packages. Which is a problem in this case because path has its type definitions in #types/node.
So let's fix that by explicitly adding node to the types array:
{
"compilerOptions": {
"types": [
"node"
],
// ...
}
// ...
}
This fixes the TypeScript error, however trying to build again we're greeted with a very similar error. This time from Webpack directly.
2. Webpack error
ERROR in ./src/app/app.worker.ts (./node_modules/worker-plugin/dist/loader.js!./src/app/app.worker.ts)
Module build failed (from ./node_modules/worker-plugin/dist/loader.js):
ModuleNotFoundError: Module not found: Error: Can't resolve 'path' in './src/app'
To figure this one out we need to dig quite a lot deeper...
Why it works everywhere else
First it's important to understand why importing path works in all the other modules. Webpack has the concept of targets (web, node, etc). Webpack uses this target to decide which default options and plugins to use.
Ordinarily the target of a Angular application using #angular-devkit/build-angular:browser would be web. However in your case, the postinstall:electron script actually patches node_modules to change that:
postinstall.js (parts omitted for brevity)
const f_angular = 'node_modules/#angular-devkit/build-angular/src/angular-cli-files/models/webpack-configs/browser.js';
fs.readFile(f_angular, 'utf8', function (err, data) {
var result = data.replace(/target: "electron-renderer",/g, '');
var result = result.replace(/target: "web",/g, '');
var result = result.replace(/return \{/g, 'return {target: "electron-renderer",');
fs.writeFile(f_angular, result, 'utf8');
});
The target electron-renderer is treated by Webpack similarily to node. Especially interesting for us: It adds the NodeTargetPlugin by default.
What does that plugin do, you wonder? It adds all known built in Node.js modules as externals. When building the application, Webpack will not attempt to bundle externals. Instead they are resolved using require at runtime. This is what makes importing path work, even though it's not installed as a module known to Webpack.
Why it doesn't work for the worker
The worker is compiled separately using the WorkerPlugin. In their documentation they state:
By default, WorkerPlugin doesn't run any of your configured Webpack plugins when bundling worker code - this avoids running things like html-webpack-plugin twice. For cases where it's necessary to apply a plugin to Worker code, use the plugins option.
Looking at the usage of WorkerPlugin deep within #angular-devkit we see the following:
#angular-devkit/src/angular-cli-files/models/webpack-configs/worker.js (simplified)
new WorkerPlugin({
globalObject: false,
plugins: [
getTypescriptWorkerPlugin(wco, workerTsConfigPath)
],
})
As we can see it uses the plugins option, but only for a single plugin which is responsible for the TypeScript compilation. This way the default plugins, configured by Webpack, including NodeTargetPlugin get lost and are not used for the worker.
Solution
To fix this we have to modify the Webpack config. And to do that we'll use #angular-builders/custom-webpack. Go ahead and install that package.
Next, open angular.json and update projects > angular-electron > architect > build:
"build": {
"builder": "#angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extra-webpack.config.js"
}
// existing options
}
}
Repeat the same for serve.
Now, create extra-webpack.config.js in the same directory as angular.json:
const WorkerPlugin = require('worker-plugin');
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin');
module.exports = (config, options) => {
let workerPlugin = config.plugins.find(p => p instanceof WorkerPlugin);
if (workerPlugin) {
workerPlugin.options.plugins.push(new NodeTargetPlugin());
}
return config;
};
The file exports a function which will be called by #angular-builders/custom-webpack with the existing Webpack config object. We can then search all plugins for an instance of the WorkerPlugin and patch its options adding the NodeTargetPlugin.

React Developer Tools Shows "Waiting for roots to load..."

I've just switched to Preact 8.4.2 and would like to get the React Developer Tools to work as well. In my webpack.config.js, I've added:
alias: {
react: 'preact-compat',
'react-dom': 'preact-compat'
}
In my entry .js file, I've added:
require('preact/debug');
After adding these, I was getting an error when attempting to build:
Module parse failed: /myProject/node_modules/preact/src/preact.js Line 1: Unexpected token
You may need an appropriate loader to handle this file type.
| import { h, h as createElement } from './h';
| import { cloneElement } from './clone-element';
| import { Component } from './component';
# ./~/preact/debug.js 6:14-31
I only had .jsx files loading with babel-loader (not .js), so I added an additional entry in my webpack.config.js file:
{
test: /\.js$/,
include: /node_modules\/preact/,
loader: 'babel-loader'
},
After adding this entry, I'm able to build without issues, but my React Developer Tools just shows:
Waiting for roots to load...
to reload the inspector [click here]
Something is either up with your preact app or your webpack config. I just solved a similar problem.
First step, check if its your preact. If you have no console errors, drop your preact into this working boilerplate (you'll have to do a little rewiring to get it working) https://github.com/developit/preact-boilerplate
If after firing it up with your code, Devtools works as expected, then there is something wrong with your build. In that case it turns into a game of line by line updating your (mostly) working build to match preact-boilerplate.
In my case, I had included "src" in my webpack resolve. This threw no errors in terminal/console, and the build worked perfectly otherwise. Once I found it, devtools started working.

Webpack fails with Node FFI and Typescript - dynamic require error

In a simple Typescript program I require Node FFI with
import * as Electron from 'electron';`
import * as ffi from 'ffi';`
and then
mylib = ffi.Library('libmoi', {
'worker': [ 'string', [ 'string' ] ],
'test' : [ 'string', [] ]
} );
Linking that up via webpack yields
WARNING in ./~/bindings/bindings.js
Critical dependencies:
76:22-40 the request of a dependency is an expression
76:43-53 the request of a dependency is an expression
# ./~/bindings/bindings.js 76:22-40 76:43-53
The problem seems to be that FFI has a dynamic require and the fix seems to be to apply webpack.ContextReplacementPlugin in the webpack.config.js file.
This is a bit out of my reach, but an example for an Angular case is:
plugins: [
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
root('./src') // location of your src
)
]
Any idea how to do this for FFI?
Here is the answer: github issue comment on the Johnny-Five repo
Quoting from brodo's answer, this is what you do to stop webpack getting snarled up with "bindings" and similar:
... the webpack config looks like this:
module.exports = {
plugins: [
new webpack.ContextReplacementPlugin(/bindings$/, /^$/)
],
externals: ["bindings"]
}
I also had a similar issue, somehow, I managed to resolve it. I will first explain my understanding.
Main work of webpack is to bundle the separate code file into one file, by default it bundles all the code that is referenced in its tree.
Generally two types of node_modules:
To be used on browser side(angular, rxjs etc)
To be used on nodejs side(express, ffi etc)
It is safer to bundle browser side node_module but not safer to bundle node side node_module because they are not designed like that So the solution is below two steps:
Give appropriate target(node, electron etc) in webpack.config.js file e.g "target":'electron-renderer' by default it is browser
Declare node_side module as external dependency in your webpack.config.js file e.g.
"externals": {
"bindings": "require('bindings')",
"ffi": "require('ffi')"
}

Webpack: expressing module dependency

I'm trying to require the bootstrap-webpack module in my webpacked application.
It appears to need jQuery, since the bundled javascript then throws the following:
Uncaught ReferenceError: jQuery is not defined
How do I go about specifying to webpack that jQuery is a dependency for the bootstrap-webpack module, to fix this issue? It feels like it should be trivial, but I've been struggling to figure it out.
I've tried adding:
"jquery": "latest"
to the dependecies in the bootstrap-webpack's package.json, but this didn't work. The documentation is incomplete, and I can't seem to find much about this issue. It should be trivial, right? Help!
There are two possible solutions:
Use the ProvidePlugin: It scans the source code for the given identifier and replaces it with a reference to the given module, just like it has been required.
// webpack.config.js
module.exports = {
...
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
};
Use the imports-loader: It provides the possibility to prepend preparations like require() statements.
// webpack.config.js
{
...
module: {
loaders: [
{ test: require.resolve("jquery"), loader: "imports?jQuery=jquery" }
]
}
}
In that case you need to run npm install imports-loader --save before.
Via this github issue.
Install expose-loader and add require('expose?$!expose?jQuery!jquery'); to your main entry point just before you require webpack-bootstrap.
This will set jQuery on the window so any file can get at it. Be careful with this method all files will then have access to that version of jQuery regardless of whether it was explicitly required.

Resources