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

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}!`);
}

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.

Webpack raw-loader errors out when require markdown file

raw-loader errors out when attempting to require any .md file.
Adding raw loader to import markdown files:
test: /\.md$/i,
use: [{
loader: 'raw-loader',
options: {
esModule: false
}
}],
In .js file, require the markdown file..
return require(postPath)
// postPath is '../posts/awards.md'
Error: Cannot find module '../posts/awards.md'
at webpackEmptyContext (eval at <path to file>)
....
the path to markdown file is the relative path: /posts/awards.md
If I change awards.md to awards.json it works. So maybe it is an issue with raw-loader looking for a export in awards.md and not find one, thus erroring out? Isn't the point of esModule: false to instruct Webpack to NOT treat it as module?
Seems like you're having the same problem as this person.
Quoting an answer:
Webpack performs a static analyse at build time.
It doesn't try to infer variables which that import(test) could be anything, hence the failure.
It is also the case for import(path+"a.js").
If you need truly dynamic imports, you have to restrict it to a known path:
import("./locales/" + locale + ".js")
I re-created your problem and sure enough:
function test(mod) {
return require(mod)
}
console.log(test("./test.md"))
Doesn't work. HOWEVER this works:
function test(mod) {
return require("./" + mod)
}
console.log(test("test.md"))
So it should suffice to change your code to this:
return require("../" + postPath + ".md")
And change postPath to:
// postPath is 'posts/awards'

webpack: question mark after [ext] in config file

webpack.config.js, under rules:
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
what does ?[hash] do?
The name parameter under the options object indicates what your file name will be evaluated to. In the [hash] case more specifically, where to place the generated content hash for the file.
So, the following config:
{
loader: 'file-loader',
options: {
name: '[path][name].[ext]?[hash]'
}
}
Would generate something like:
path/to/file.png?e43b20c069c4a01867c31e98cbce33c9
The purpose for that is ability to easily invalidate these files when new versions are available. By having an unique hash to each file version,
the browser will discard the old one when there's a new version
You can specify hash types (md5, sha256, etc) and other configurations, read more:
https://webpack.js.org/loaders/file-loader/
Someone can correct me if I'm wrong, but I believe it has to do with adding a query string to bypass browser caching so that when you recompile, the newest version of your resources (source code or other resources) is loaded instead of an old cached version. And the hash is just so that the query string is based on the content of your raw resource.
hash is basically calculated for a build.
hash returns the build hash. If any portion of the build changes, this changes as well. check here and here for more details

'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