webpack - how to stop the loader running twice? - javascript

All of my text imports in a big project are in the form:
var template = require('text!./foo.html');
I'd like to set webpack to automatically use text-loader, so I added the following to my config:
{ test: /\.html$/, loader: 'text-loader' }
Only problem is now my templates are being run through the loader twice, and I'm getting something like this in my bundle...
module.exports = 'module.exports = "<section class=\\"foobar\\" ...
How can I set the loader to only run once without removing all of the text! callouts from every one of my files? This isn't an option as I'm trying to migrate incrementally...

require('text!./foo.html') applies text-loader to the foo.html
{ test: /\.html$/, loader: 'text-loader' } applies text-loader to every html
Hence,your loader is applied twice.
You should remove text-loader from either of two and it will work fine

Related

Render multiple pages unrelated to the main app with Webpack and Mustache

I'm developing a Chrome Extension and I use Webpack to bundle it. I've got my compiled bundle, which is the main part of the app, but I also need an options page to describe the functionality. This options page has nothing to do with the bundle, it's just a static HTML file.
I must put a lot of things in that options page so I want to render that page with Mustache and define all content with JavaScript. For the most part, I've done that.
Here's my Webpack config (I've removed the parts regarding my app):
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
output: {
path: path.join(__dirname, 'extension/build/')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/options/index.html',
inject: false
})
],
module: {
rules: [
{
test: /\.html$/,
loader: 'mustache-loader',
options: {
render: require('./src/options/index.js')
}
}
]
}
}
and in my src/index.js, I have:
require('./options/index.html')
This will open the template and render it with the data in src/options/index.js.
There's a problem with that, however. I run Webpack with webpack --watch and changes to index.js (it holds the template data) do not trigger a rebuild. Also, I would need to go through a lot of trouble to create another static HTML file in the same manner.
It would be ideal if HtmlWebpackPlugin automatically used the template I require() in my entry point so that I don't need to explicitly set it. Also, it would be great if it automatically used a js in that same location to get the data. For example:
require('./options/index.html`)
Renders the template with data from ./options/index.html.js and then emits it. It would be even better if it emitted it to a custom folder specified in the Webpack config.
Is that possible? I couldn't find a plugin/loader that does that.
Edit: I was able to partly fix the rebuild problem by specifying the render option as a function:
{
test: /\.html$/,
loader: 'mustache-loader',
options: {
render () {
var file = './src/options/index.js'
delete require.cache[require.resolve(file)]
return require(file)
}
}
}
But it still doesn't work properly. The rebuild would only trigger after I make changes to index.html. This means that if I change index.js, I need to go and save index.html as well to trigger the build.

Loading SVGs within .scss with Webpack and svgr loader

Webpack config:
For a .svg I use config:{ test: /\.svg$/, use: ['svgr/webpack'] }
For .scss I use css-loader, postcss-loader and sass-loader
Folder structure:
I have folder structure that looks like this:
- App
-- styles
--- globals.scss // Here I import my partials
--- partials
---- _my_partial.scss
-- icons
--- svgs
---- my_icon.svg
svgr loader:
I like svgr loader as it allows me to just import my icon and use it as React component:
import MyIcon from './icons/svgs/my_icon.svg';
...
<MyIcon />
The actual problem:
I was fine with this approach but I have to get one of the svgs as a background-image, so inside _my_partial.scss I wrote:
background-image: url(../icons/svgs/my_icon.svg);
I am up just one folder in this url as when being up two, it complained that it cannot resolve it - I guess this is because I import my partials in my globals.scss.
With this setup all I get in the browser is:
GET http://localhost:3005/[object%20Module] 404 (Not Found)
svgr/webpack turns your svg into react components, so when using svg into scss it's actually an object / react component. Change svgr/webpack to file-loader in order to use that. If you want to still use both, you could try something like:
{ test: /\.react.svg$/, use: ['svgr/webpack'] }
{ test: /\.svg$/, use: ['file-loader'] }
then rename all the svg's that you want as React components to filename.react.svg and the rest just leave with .svg.
I haven't tested this though :)
UPDATE: Looking at the documentation (section: Handle SVG in CSS, Sass or Less), it seems you can use svgr/webpack with file-loader:
https://github.com/smooth-code/svgr/tree/master/packages/webpack
{
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
issuer: {
test: /\.jsx?$/
},
use: ['babel-loader', '#svgr/webpack', 'url-loader']
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader'
},
}
Either way, you probably need to make a few changes to fit in your needs but it supports it :)

Import Javascript files as a string?

I want to be able to simply do an import file from 'file.js and then have file be a string of the contents within file.js. I've toyed around with raw-loader but it doesn't give me the same contents (instead it loads it in a different format). Any suggestions?
it doesn't give me the same contents (instead it loads it in a different format)
That seems to mean that there are other loaders from your config applied to the file. You can enforce that only the loader in the import statement is used by prefixing it with a !:
import file from '!raw-loader!file.js'
From the docs:
It's possible to overwrite any loaders in the configuration by prefixing the entire rule with !.
In Webpack 5 it's possible to handle it without raw-loader.
It's enough to add a rule with type: asset/source (see the docs). Note that in this case, if you use babel loader or other JS loaders, the code will still be processed by them if not overridden manually.
A simplified code example:
module: {
rules: [
{
test: /(?<!boilerplate)\.js$/, // a negative look-behind regex to exclude your file
exclude: /node_modules/, // also can be handled here, adding a folder with file(s) to be excluded
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /boilerplate\.js$/,
type: 'asset/source'
},
]
}

How can we conditionally load a vendor css with webpack?

For example, if we have a vendor scss file named vendor.scss with the following content:
$color: green;
h1 {
background-color: $color;
}
How can we write the rules to achieve to following result? (Or similar)
let vendor = true; // this will be configured outside
if (vendor) {
let style = require('style-loader/useable!css-loader!./vendor.scss');
style.use(); //loads just if I call `use`, otherwise not
}
require('my-other-file.scss'); // loads normally, without `use`.
Because vendor.scss is a vendor scss/css we cannot or should not rename it to, for example, vendor.useable.scss.
And for maintaining purposes we would like to not create a new one like my-vendor-wrapper.scss that could wrap it with an #import inside of it.
In webpack, for now, I have this rule:
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
// test: /\.(scss|sass|css)$/i,
test: /^(?!.*style-loader\/useable!).*\.(sass|scss|css)$/i,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'postcss-loader', 'sass-loader']
})
}
I tried several things, like creating additional rules.
If I use an custom extension like lscss (for lazy scss) I can put it to work.
But the expected behavior that I would like is load all css/scss normally but if it have style-loader/useable! in front of it just loads if I call use.
How can we do this?

Conflicting server/client rendering and Webpack’s local css modules

I'm using Fluxible to help create an isomorphic app on a new project and it's going swimmingly. I love it so far. I have run into a speed bump, though, and wonder how to get over it.
Here is my Header component thus far:
import React from 'react'
import Nav from '../Nav/Nav'
import classNames from 'classnames'
if (process.env.BROWSER) var styles = require('./Header.css')
class Header extends React.Component {
render() {
// Header classes
var theClasses = process.env.BROWSER ? classNames({
[styles.Header]: true
}) : ''
return (
<header className={theClasses}>
<Nav selected={this.props.selected} links={this.props.links} />
</header>
)
}
}
export default Header
You'll see I'm using process.env.BROWSER to detect which ENV I'm on. If we're in the client, I require the CSS. If we're on the server, I skip it. That works wonderfully.
The problem comes later in the file, where I build theClasses object based on the contents of the Header.css file, then use those classes on the Header like so:
<header className={theClasses}>
<Nav selected={this.props.selected} links={this.props.links} />
</header>
The problem is because I'm not loading the css on the server, theClasses ends up being empty, and the content rendered for the client ends up different than the content on the server. React displays this warning:
Warning: React attempted to reuse markup in a container but the
checksum was invalid. This generally means that you are using server
rendering and the markup generated on the server was not what the
client was expecting. React injected new markup to compensate which
works but you have lost many of the benefits of server rendering.
Instead, figure out why the markup being generated is different on the
client or server:
(client) n28"><header class="Header--Header_ip_OK
(server) n28"><header class="" data-reactid=".2ei
What would you recommend to get this resolved?
UPDATE September 24, 2015
The original issue was that I couldn't get CSS to compile on the server side, so I started checking for the BROWSER like this:
if (process.env.BROWSER) var styles = require('./Application.css')
If I remove the if (process.env.BROWSER) bit I get this error:
SyntaxError: src/components/Application/Application.css: Unexpected token (2:0)
1 |
> 2 | #import 'styles/index.css';
| ^
3 |
In the following simple CSS file:
#import 'styles/index.css';
.Application {
box-shadow: 0 0 0 1px var(--medium-gray);
box-sizing: border-box;
lost-center: 1080px 32px;
}
I started this project with the Fluxible Yo Generator which provides two Webpack config files here: https://github.com/yahoo/generator-fluxible/tree/master/app/templates
I updated mine with a few loaders:
var webpack = require('webpack');
var path = require('path');
module.exports = {
resolve: {
extensions: ['', '.js', '.jsx']
},
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./client.js'
],
output: {
path: path.resolve('./build/js'),
publicPath: '/public/js/',
filename: 'main.js'
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loaders: [
require.resolve('react-hot-loader'),
require.resolve('babel-loader')
]
}, {
test: /\.css$/,
loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]_[hash:base64:5]!postcss-loader'
}, {
test: /\.(png|jpg|svg)$/,
loader: 'url?limit=25000'
}, {
test: /\.json$/,
loader: 'json-loader'
}
]
},
postcss: function () {
return [
require('lost'),
require('postcss-import')({
path: ['./src/'],
onImport: function (files) {
files.forEach(this.addDependency);
}.bind(this)
}),
require('postcss-mixins'),
require('postcss-custom-properties'),
require('autoprefixer')({
browsers: ['last 3 versions']
})
];
},
node: {
setImmediate: false
},
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
BROWSER: JSON.stringify(true)
}
})
],
devtool: 'eval'
};
So that's where I am… not sure how to get CSS compiled server side. Appreciate any help I can get.
You can try css-modules-require-hook
webpack.config.js
{
test: /\.css$/,
loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]'
}
server.js
require('css-modules-require-hook')({
// This path should match the localIdentName in your webpack css-loader config.
generateScopedName: '[name]__[local]___[hash:base64:5]'
})
You need to ensure the server is generating the same markup as the client, which means that you need to use CSS Modules on the server too.
We achieve this in my current project by using Webpack to compile our Node code too. James Long wrote a really good guide on how to set this up, spread across three blog posts:
Backend Apps with Webpack (Part I)
Backend Apps with Webpack (Part II)
Backend Apps with Webpack (Part III)
You should certainly be rendering your server side HTML with the classes otherwise you are missing out on the time to glass perks of an isomorphic application. It means you will still have to wait for the JS to load in order to apply the CSS styles which defeats some of the purpose of building the HTML server side. It also means you can't "cut the mustard" and serve old browsers an application with Javascript turned off.
The question is why aren't you rendering the CSS classes on the server? This also confused me for a while but my guess is you don't have two Webpack entry points? One for the client, and one for the server.
If my assumption is correct then take a look at a sandbox repo here where I'm doing multiple builds for the Node server entrypoint and the Browser entrypoint also using Fluxible.
However I would take it all with a pinch of salt for now as it was just a test project for personal use. I have also used this approach with local css where it builds it on both the server and client and it works like a charm.
EDIT: I see you are using ES6 so I assume you are indeed building server side? If so what is the reason you don't include the CSS?
So you have to have two Webpack configs, the first is for compiling browser bundle, the second is for compiling server bundle.
With a standard configuration you have css-loader?...:
{
test: /\.css$/,
loader: 'style-loader!css-loader?modules&localIdentName=[name]__[local]_[hash:base64:5]'
}
And you get two different bundles.
On a server. All classes are in css.locals object:
let css = require('styles.scss');
css.locals == { fooClass: 'some_foo_class' }
On a browser there is no .locals:
let css = require('styles.scss');
css == { fooClass: 'some_foo_class' }
So you need to get rid of .locals in a server version, so that it should be like in a browser.
You can do it using css-loader/locals?... in a webpack server config:
{
test: /\.scss$/i,
loader: "css/locals?modules&importLoaders=1&-minimize&localIdentName=[local]_[hash:3]!sass"
}
More details here https://github.com/webpack/css-loader/issues/59#issuecomment-109793167

Categories

Resources