I've been banging my head over this for a while, and am wondering if it's even possible to begin with. Thanks for any help with this!
The npm package
I've got an npm package which is basically a library of React components. This library has embedded stylesheets, which references assets like fonts and images from the CSS. These are then all bundled using webpack into my-package.js.
The config for this looks like:
var path = require('path');
module.exports = {
module: {
loaders: [
{
test: /\.js$/,
loaders: ['babel-loader'],
exclude: /node_modules/
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
loader: 'file-loader'
},
{
test: /\.styl$/,
loader: 'style-loader!css-loader!stylus-loader'
}
]
},
entry: [
'./lib/components/index.js',
'./lib/index.styl'
],
output: {
path: path.join(__dirname, 'build/'),
filename: 'my-package.js'
}
}
With ./lib/components/index.js looking like:
import '../index.styl';
import MyComponent from './my-component.js';
export {
MyComponent
}
So far, so good.
The application
Now in another code base I've got the main application, which install this npm package.
My application root requires this package...
import MyPackage from 'my-package';
And is then itself webpack bundled and loaded onto the browser. All the scripts and style blocks are bundled correctly, however the styles which reference the assets are using the relative url from the npm package itself, therefore giving me 404s from the application.
console errs
Is there any way to tell webpack to resolve these images from node_modules/my-package/build/[webpack-generated-name].jpg ?
My application's webpack config looks like this:
var path = require('path'),
webpack = require('webpack');
module.exports = {
devtool: '#eval-source-map',
entry: [
'my-package',
'webpack/hot/only-dev-server',
'./app/index.js',
],
output: {
path: path.join(__dirname, 'build/static'),
filename: 'bundled.js',
publicPath: '/',
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
resolve: {
extensions: ['', '.js']
},
resolveLoader: {
'fallback': path.join(__dirname, 'node_modules')
},
module: {
loaders: [
{
test: /\.js$/,
loaders: ['react-hot', 'babel'],
exclude: /node_modules/,
include: __dirname
},
{
test: /\.css?$/,
loader: "style-loader!css-loader",
include: __dirname
},
{
test: /\.(jpg|jpeg|ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
loader: 'file-loader'
},
{
test: /\.styl$/,
loader: 'style-loader!css-loader!stylus-loader'
}
]
}
};
Figured out a way around this.
In my application's webpack config I added a plugin (recommended by #Interrobang) which copies the static assets from the node_module/my-package into the app server's public path:
var TransferWebpackPlugin = require('transfer-webpack-plugin');
...
plugins: [
new TransferWebpackPlugin([
{ from: 'node_modules/my-package/assets', to: path.join(__dirname, 'my/public') }
])
]
...
These will then be made accessible by calling the asset name: localhost:XXXX/my-image.jpg. The server here is basically looking at /my/public/my-image.jpg if you've set it up correctly.
I'm using Express, so I just had to define app.use(express.static('my/public')) in my app server.
When bundling your NPM package, you could inline all the static assets into your final bundle. That way, your index.js that Webpack bundles for you would have all the static assets it needs, and simply importing that wherever you need your React components would do the trick.
The best way to inline static assets as of Webpack 5 is by using the "asset/inline" module from Asset Modules, which you can read about here: https://webpack.js.org/guides/asset-modules/
Simply put, your webpack.config.js should have a section as such:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
type: 'asset/inline'
}
]
},
};
What really relates to your question here is the test for png, jpg, and gif files which uses the asset/inline module.
The post here https://stackoverflow.com/a/73417058/14358290 explains it with slightly more detail.
Other plugins that copy such files from your /node_modules to /build directory are hacky and create packages that are not really distributable - everyone else that uses the said package would have to set up their Webpack to do the same copying operation. That approach can be avoided now that Webpack 5 solves this problem for us.
Related
I'm migrating a project from using Grunt to using Webpack. This project uses jQuery. I noticed that the bundled code was working fine, even though I hadn't yet added jQuery to package.json, which seemed strange.
Looking at the output of webpack --mode=development --display-modules, I saw:
[../../../../../../../node_modules/jquery/dist/jquery.js] /Users/rothomas/node_modules/jquery/dist/jquery.js 274 KiB {index} [built]
That is: it seems at some point I ran npm install --global jquery, and Webpack is importing that jQuery. I don't want this to happen, because my teammates/server won't have jQuery installed in $HOME.
The obvious solution is for me to just remove jQuery from my $HOME/node_modules (no idea how it got there anyway), which will cause Webpack to fail until I add it to package.json, as expected.
But I'd like to know:
Why does Webpack use $HOME/node_modules? I understand this is the default behavior of Node package resolvers, but it seems very error-prone since I imagine many other developers keep their projects nested under $HOME.
How can I specify the scope within which Webpack should be trying to resolve modules?
(I looked at Webpack's documentation on resolvers, but it's not very clear to me.)
Here is my current Webpack config:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
function pathTo(filepath) {
return path.join(__dirname, filepath);
}
module.exports = function (env, argv) {
return {
entry: {
'index': [
pathTo('src/scripts/index.js'),
pathTo('src/scss/index.scss'),
]
},
module: {
rules: [
{
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'#babel/env'
]
},
test: /\.js$/,
},
{
test: /\.(scss|css|sass)$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader',
options: {
url: false,
},
},
{
loader: 'sass-loader',
options: {
sassOptions: {
outputStyle: 'expanded',
},
},
},
],
},
],
},
output: {
filename: '[name].js',
path: pathTo('web'),
},
plugins: [
new MiniCssExtractPlugin()
],
}
}
The problem is that your app is located inside the directory that has your global node_modules directory.
Webpack (and for that matter all node resolvers) will keep searching up your tree until it finds a directory that has a node-modules directory. then it will check in there for jquery. It continues doing this until it either finds what it's looking for, or if it reaches the root of your filesystem.
I have been trying to use uglify option using webpack, but my resultant page's size remains the same without minification.I tried the following things,
webpack.config
var webpack = require('webpack');
const Uglify = require("uglifyjs-webpack-plugin");
module.exports = {
entry: __dirname + '/app/index.js',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
output: {
filename: 'whattoname.js',
path: __dirname + '/build'
},
plugins: [
new Uglify()
]
};
I tried to set the mode to production
Ran the build using webpack -p command
Also with --optimize-minimizer command
The end file's size remains the same. Am I missing something here?
I had a similar issue, to resolve the problem I would suggest moving across to the inbuilt webpack uglifier as seen in the following example (no uglifier dependancy required):
var webpack = require('webpack');
module.exports = {
entry: __dirname + '/app/index.js',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
output: {
filename: 'whattoname.js',
path: __dirname + '/build'
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
minimize: true
})
]
};
If this does not resolve your issue I suggest several actions:
clean out your dist and recompile to insure the file is actually writing to dist
Inspect the dist code, to check if it appears uglified. It is possible your project was already uglifying the file somewhere else, which would mean the file size after uglification does not change
Adding the include: /\.js$/ field to your webpack.optimize.UglifyJsPlugin to specify precisely the targeted files
As for what caused this issue. I would suggest reading this comments posted here
I'm using ASP.NET Core 2.0. If anyone wants to see detailed code or run it themselves, the code can be found here: https://github.com/jakelauer/BaseballTheater/tree/master/BaseballTheaterCore
My basic problem is that I'm expecting each generated js file in my project to have a sourcemap back to the original .ts or .tsx file. That is not working except for my entry file (./ClientApp/boot.tsx).
Here is my webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'main': './ClientApp/boot.tsx' },
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: 'dist/'
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader?minimize' }) },
{
test: /\.scss/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('site.css')
])
}];
};
Based on my interpretation of this file and limited understanding of webpack, this should work. Each of my files does generated a .js.map file, and it appears to be referenced in the generated .js file. However, none of them actually load except the one for boot.tsx when debugging in Chrome.
An example of one of the js files in Chrome:
And that file does have the correct files to load:
When I open main.js.map in /wwwroot/dist/ and Ctrl+F for ts inside there, I only find boot.tsx and none of the other .ts or .tsx files I would expect to find.
I am no webpack expert, so I'm not sure what else to do!
From the comments, we've come to the solution by:
upgrading to the newest webpack (v4 in this case) and
installing the source-map loader via npm install --save-dev source-map-loader and
setting devtool: 'source-map' in the webpack.config.js.
The source-map option tells webpack to emit a full separate source map file. This is from the webpack docs:
source-map - A full SourceMap is emitted as a separate file. It adds a reference comment to the bundle so development tools know where to find it.
I have a folder of around 20 separate less files that I need to concatenate into a single file via Webpack and store this in my /dist folder. My current Webpack config file is as follows:
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const bundleOutputDir = './wwwroot/dist';
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
return [{
stats: { modules: false },
entry: { 'main': './ClientApp/boot.ts' },
resolve: { extensions: ['.js', '.ts'] },
output: {
path: path.join(__dirname, bundleOutputDir),
filename: '[name].js',
publicPath: '/dist/'
},
module: {
rules: [
{ test: /\.ts$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.html$/, use: 'raw-loader' },
{ test: /\.css$/, use: isDevBuild ? ['style-loader', 'css-loader'] : ExtractTextPlugin.extract({ use: 'css-loader' }) },
{ test: /\.less/, use: ExtractTextPlugin.extract('style-loader', 'css-loader!less-loader') },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [
new CheckerPlugin(),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin('site.less'),
new ExtractTextPlugin('site.css')
])
}];
};
If I try and import each single .less file into the boot.ts entry file, I get a less error stating that the less variables that I've declared are not being recognised, which is how I came to the conclusion that I need to concat these files beforehand. I come from a gulp background, so any help to get me up and running with this would be greatly appreciated.
If there is an alternative way to get all less compiled to css and working correctly, without the need for concat, then I'm open to suggestions.
Webpack is a module bundler and uses the module syntax for JavaScript (ES6, CommonJS, AMD..), CSS (#import, url) and even HTML (through src attribute) to build the app's dependency graph and then serialize it in several bundles.
In your case, when you import the *.less files the errors are because you miss CSS modules. In other words, on the places where you have used variables defined in other file, that file was not #import-ed.
With Webpack it's recommended to modularize everything, therefore I would recommend to add the missing CSS modules. I had the same issue when I was migrating a project from Grunt to Webpack. Other temporary solution is to create an index.less file where you will #import all the less files (note: the order is important) and then import that file in app's entry file (ex. boot.ts).
I followed this tutorial to setup a react project and I thought I nailed it. After installing everything works as it should.
But I have to create a website that uses foundation as a front-end lib. The tutorial I linked above runs server.js when I run npm start but foundation-cli uses the same command for compiling all foundation sass code to css.
At the moment when I run npm start it only runs react and not foundation. How can I run foundation too? can I make it so that it'll automatically run both?
Since you are already using Webpack, I would suggest you to use one of the loaders for it and compile your CSS with it. This one seems like it can do that for you.
This is not tested, but I assume you can add it to your Webpack config like this:
// be sure to install it with
// npm install sass-loader node-sass --save-dev
const path = require('path');
module.exports = {
context: path.join(__dirname, 'src'),
entry: [
'./main.js',
],
output: {
path: path.join(__dirname, 'www'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
'babel-loader',
],
},
{
test: /\.scss$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}
}
],
},
resolve: {
modules: [
path.join(__dirname, 'node_modules'),
],
},
};