I have a React component, which I'm building with Webpack. The component is exported from index.js, which is the entry file in the module:
// packages/tnetennba/index.js
class Tnetennba extends React.Component {
render() {
return (
<div>good morning, that's a nice tnetennba</div>
)
}
}
export default Tnetennba
My webpack configs resides in different directory than the module (I'm using mono-repository), so the module itself doesn't have any build configuration files or templates to work with webpack-dev-server.
// packages/tnetennba/package.json
{
"name": "tnetennba",
"version": "0.0.1",
"main": "dist/index.js",
"scripts": {
"start": "webpack-dev-server --config ../config/webpack.dev.js --open",
"build": "webpack --config ../config/webpack.build.js"
}
}
However, I wan't to be able to work with the component in the dev mode, and I can achieve that using HtmlWebpackPlugin:
// packages/config/webpack.dev.js
module.exports = merge(common, {
mode: 'development',
...
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: '../config/index.html' // <-- this path is relative, becuase webpack will be called from packages/tnetennba directory.
})
]
})
However, I have to put ReactDOM.render function call inside my module's index.js, to render component into template:
//...
export default Tnetennba
ReactDOM.render(<Tnetennba />, document.getElementById('root')) // <-- This shouldn't be here.
Question: Is there a way, that I can avoid calling render inside the module, while working in dev mode? Is is possible to call React renderer within webpack?
I would like to avoid conditional rendering using node's process variable as well.
Look at HtmlWebpackIncludeAssetsPlugin, it allows you to add an asset to the html file, my idea is to add a js file that will call ReactDom.render method inside it.
new HtmlWebpackPlugin(),
new HtmlWebpackIncludeAssetsPlugin({
assets: [
{ path: 'path/to/bootstrap.js', type: 'js' }
]
})
Related
Getting an error with nextjs when using 'asset/inline' Asset Module Type in custom webpack config when running yarn dev. I was trying to use the 'asset/inline' asset module type to export the URI of the imported image inline but got this error. However, using 'asset' instead of 'asset/inline' shows no error.
Error:
error - ./pages/index.js:2:0
Module not found: Invalid generator object. Asset Modules Plugin has been initialized using a generator object that does not match the API schema.
- generator has an unknown property 'filename'. These properties are valid:
object { dataUrl? }
-> Generator options for asset/inline modules.
1 | import React from 'react';
> 2 | import test from '../test.jpeg'
3 |
4 | const Page = () => {
5 | return (
https://nextjs.org/docs/messages/module-not-found
package.json:
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"eslint": "8.10.0",
"eslint-config-next": "12.1.0"
}
}
next.config.js:
module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.jpeg/,
type: 'asset/inline',
})
return config
},
}
pages/index.js:
import React from 'react';
import test from '../test.jpeg'
const Page = () => {
return (
<>
<div>Index Page</div>
<img src={test} alt="pulkit"/>
</>
)
}
export default Page;
Folder Structure:
app
|--node_modules
|--package.json
|--test.jpeg
|--next.config.js
|--pages
|--index.js
UPDATED
This article also mentioned your related configs
https://survivejs.com/webpack/loading/images/
type: "asset/inline" emits your resources as base64 strings within the
emitted assets. The process decreases the number of requests needed
while growing the bundle size. The behavior corresponds with
url-loader.
You can try this out instead
config.module.rules.push({
test: /\.jpeg/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 15000 } },
})
If you are concerned about the image performance, you can try next/image which has a bundle of image performance helpers.
Another aspect, I'd also like to tell you that with asset/inline, all your images will be converted to base64 and embedded in HTML which will increase your initial web loading time. (https://bunny.net/blog/why-optimizing-your-images-with-base64-is-almost-always-a-bad-idea/)
OLD ANSWER
From my understanding, asset/inline is for data URIs with prefix like this data: (https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). The problem with your case is images are in bytes and cannot convert to correct formats for display (if your images are base64, you may be able to use asset/inline)
I'm personally using asset/resource instead to have URIs
You can modify your configs this way
module.exports = {
webpack: (config, options) => {
config.module.rules.push({
test: /\.jpeg/,
type: 'asset/resource',
})
return config
},
}
Here is a very useful article to help you understand it deeper
https://dev.to/smelukov/webpack-5-asset-modules-2o3h
I found how we use assets in Webpack 5 here too
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource'
},
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline'
},
Hopefully, it can help you to solve your problem
You don't need the extra custom webpack configuration to import images, Next.js already provides a built-in way to statically import local images.
Remove your custom webpack config, then modify your component's code according to the following snippet.
import React from 'react';
import test from '../test.jpeg'
const Page = () => {
return (
<>
<div>Index Page</div>
<img src={test.src} alt="pulkit"/>
</>
)
}
export default Page;
In addition to the src field, you also have access to the width and height of the image if you wish to use that in the <img> element.
From the Image Optimization documentation:
Next.js will automatically determine the width and height of your
image based on the imported file. These values are used to prevent
Cumulative Layout
Shift while your
image is loading.
I am trying to create a component library wie rollup and Vue that can be tree shakable when others import it. My setup goes as follows:
Relevant excerpt from package.json
{
"name": "red-components-with-rollup",
"version": "1.0.0",
"sideEffects": false,
"main": "dist/lib.cjs.js",
"module": "dist/lib.esm.js",
"browser": "dist/lib.umd.js",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w"
},
"devDependencies": {
/* ... */
}
And this is my entire rollup.config.js
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import vue from "rollup-plugin-vue";
import pkg from "./package.json";
export default {
input: "lib/index.js",
output: [
{
file: pkg.browser,
format: "umd",
name: "red-components"
},
{ file: pkg.main, format: "cjs" },
{ file: pkg.module, format: "es" }
],
plugins: [resolve(), commonjs(), vue()]
};
I have a fairly simple project structure with an index.js file and 2 Vue components:
root
∟ lib
∟ index.js
∟ components
∟ Anchor.vue
∟ Button.vue
∟ package.json
∟ rollup.config.js
My index.js imports the Vue files and exports them:
export { default as Anchor } from "./components/Anchor.vue";
export { default as Button } from "./components/Button.vue";
export default undefined;
If I don't do export default undefined; somehow any app importing my library cannot find any exports. Weird.
Now when I create another app and I import red-components-with-rollup like so:
import { Anchor } from "red-components-with-rollup";
and I open the bundle from my app, I will also find the source code of the Button.vue in my bundle, it has not been eliminated as dead code.
What am I doing wrong?
What is the build result of the ES format? Is it a single file or multiples, similar to your sources?
Considering your Rollup options, I’m guessing it bundles everything into a single file, which is most probably the reason it isn’t able to tree-shake it.
To keep your ES build into multiple files, you should change:
{ file: pkg.module, format: "es" }
Into:
{
format: "es",
// Use a directory instead of a file as it will output multiple
dir: 'dist/esm'
// Keep a separate file for each module
preserveModules: true,
// Optionally strip useless path from source
preserveModulesRoot: 'lib',
}
You’ll need to update your package.json to point module to the new build file, something like "module": "dist/esm/index.js".
There are some interesting pitfalls with tree shaking that this article covers that you might be interested in.
Other than that - does your build tooling for your consumer app support pure es modules and have tree shaking capabilities? If so, then i would just make sure your exported files are not doing any 'side-effecty' things that might confuse rollup.
To be on the safe side i would offer direct imports to for each of your components as well as one main index.js that exports them. At least you're giving people who are paranoid of shipping unused code the option ie -
import { Anchor } from "red-components-with-rollup/Anchor";
I'm writing a library that needs to run in nodejs, and in the browser (imported via script tag).
I want to use typescript to write it, because it's convenient, also I want to use webpack, because at some point I may have other contents or styles to associate to it.
So far, I've succeded in making the library available to node and to the browser, but somehow, I can't simply import it in another typescript project and use the fact that it's a typescript library.
This is how my tsconfig.json look:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"target": "es6",
"outDir": "ts/",
"strict": true
}
}
and this is my webpack.config.js:
const path = require('path');
// PLATFORMS
var variants = [
{
target: "web",
filename: "index.browser.js"
},
{
target: "node",
filename: "index.node.js"
},
];
// CONFIG
var configGenerator = function (target, filename) {
return {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ]
},
target: target, // "web" is default
output: {
library: "myWebpackTypescriptLib",
libraryTarget: "umd",
filename: filename,
path: path.resolve(__dirname, 'dist')
}
};
};
// MAP PLATFORMS WITH CONFIGS
module.exports = variants.map(function(variant){
return configGenerator(variant.target, variant.filename)
})
From another typescript project, I tried to import it like this:
import myWebpackTypescriptLib from "myWebpackTypescriptLib";
But with this import, in my text editor, I wouldn't have access to typescript definitions, and at runtime I got the following error: ReferenceError: require is not defined.
I also tried the followings, hoping I may have better results:
import * as myWebpackTypescriptLib from "myWebpackTypescriptLib/src/index"
import * as myWebpackTypescriptLib from "myWebpackTypescriptLib/dist/ts/index.d"
With those, I succeded in getting the typescript definition, but not to have a running code.
I tried various configurations of module and target in compiler, but somehow, the present one gave me the best results. At least two on three of the things I'm trying to do work.
I've followed many different tutorials on how to achieve similar thing, but I never got to have those three elements working. I must still be missing something.
Any idea?
EDIT:
This is how my directory structure look like:
All the code is located in "/src/" directory, and webpack creates versions for browser and node in "/dist/".
The file /src/index.ts imports codes from other files in additionals, plugins, utilities, it looks like this:
// UTILITIES
export * from "./utilities/someUtility";
// ADDITIONALS
export { someModule } from "./additionals/someModule";
let test = 10;
let echo = (a:any) => {
return a;
}
export default test;
export { echo };
All the import and export are properly set, since everything is rendered well by webpack for the browser and node.
So I finally succeeded to make it work.
I was missing a proper declaration of my types in the library's package.json, this is how it look now, and it works :)
{
"name": "myWebpackTypescriptLib",
"version": "0.0.1",
"description": "A webpack typescript library, usable in browser, node and other typescript projects.",
"main": "dist/index.node.js",
"types": "dist/ts/index.d.ts", // <---- this here was not properly set, it gives access to the typescript definitions when importing from other typescripts files
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode=development --progress --config webpack.config.js",
"prod": "webpack --mode=production --progress --config webpack.config.js"
},
...
}
I make my project by vue-cli.
vue init webpack vue-demo
cd vue-demo
npm install
npm run dev
Now I want to devolop some components. And i want to use them in requirejs.
webpack config
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js',
libraryTarget: 'umd',
library:'senyint'
}
Q1:It generate three files. app.js manifest.js vendor.js
The demo has a Hello.vue . I want to require the js file by what webpack generate.
But I require them,it's undefiend . Why? What's the wrong?
Where I should export ?
Now I export in main.js like this.
import Hello from 'components/Hello'
module.exports = {
Hello
}
Q2:I dont want to package without vue.
So i configure this
externals: {
vue: 'vue'
}
If i do this, when npm run dev show error "Uncaught TypeError: Cannot read property 'config' of undefined"
It cause cant find Vue.
If i configure externals vue how to make it run?
Q1:
Open the javascript file app.js i found
define("senyint", ["vue"], factory);
at this line. The 'senyint' is a package name,webpack generate the js,but it doesnt work.
I modify the code like this
define(["vue"], factory);
Require it and it works. But I dont know why.... I just can resolve this problem;
main.js export .vue components
import Hello from 'components/Hello.vue'
export const scom = {
Hello
}
requirejs config
requirejs.config({
baseUrl: 'js/common',
paths: {
module: '../modules'
},
shim: {
app: {
deps:['vue','manifest','vendor']
}
}
})
requirejs(['module/demo', 'app'], function (demojs, app) {
debugger
console.log(app)
})
Q2:
I builded my project with vue-cli webpack template. (not webpack-simple)
In build directory has 'webpack.base.conf.js' and 'webpack.prod.conf.js'
Modify webpack.prod.conf.js add
externals: {
vue: {
root: 'Vue',
commonjs: 'vue',
commonjs2: 'vue',
amd: 'vue'
}
},
and dont add the code in 'webpack.base.conf.js' .Then npm run build it will package without vue.js .Npm run dev use webpack.base.conf.js ,it will run with vue
I'm currently playing around with React Native. I'm trying to structure my app, however it's starting to get messy with imports.
--app/
-- /components
-- Loading.js
-- index.ios.js
Now, within my index.ios.js i'm able to simply do:
import Loading from './components/Loading';
However, when I start to create more components, with a deeper directory struture, it starts to get messy:
import Loading from '.../../../../components/Loading';
I understand the preferred solution would be to make private npm modules for things, but that's overkill for a small project.
You could do a global.requireRoot type solution on the browser, but how do I implement this with import?
Had the same issue with React.
So i wrote some plugin for babel which make it possible to import the modules from the root perspective - the paths are not shorter - but it's clear what you import.
So instead of:
import 'foo' from '../../../components/foo.js';
You can use:
import 'foo' from '~/components/foo.js';
Here is the Plugin (tested and with a clear README)
The react documentation explain how to do that:
https://create-react-app.dev/docs/importing-a-component/#absolute-imports
just add a jsconfig.json in your project root:
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
If you are using Webpack you can configure it via the resolve property to resolve a your import path.
Webpack 1
resolve: {
root: [
path.resolve(__dirname + '/src')
]
}......
Webpack 2
resolve: {
modules: [
path.resolve(__dirname + '/src'),
path.resolve(__dirname + '/node_modules')
]
}.....
After that you can use
import configureStore from "store/configureStore";
instead of the:
import configureStore from "../../store/configureStore";
Webpack will configure your import path from the passed resolve param.
The same stuff you can do with System.js loader but with it's own config param (it's can be map or path. Check it in the System.js documentation) (if you would like to use it. It's mostly for a Angular 2 case. But I suggest: don't use standard System.js even if you are working with ng2. Webpack is much better).
I just checked out a React project which is more than 6 months old and for some reasons my imports no longer worked. I tried the first answer:
import 'foo' from '~/components/foo.js';
Unfortunately this did not work.
I added an .env file in the root of my project at the same level as my package.json. I added the following line to that file and this fixed my imports in my project.
NODE_PATH=src/
If you're using Create-React-App, you just need to change the environmental variable NODE_PATH to contain the root of your project.
In your config.json do the following change to set this variable before running the react-scripts commands:
"scripts": {
"start": "cross-env NODE_PATH=. react-scripts start",
"build": "cross-env NODE_PATH=. react-scripts build",
"test": "cross-env NODE_PATH=. react-scripts test",
"eject": "react-scripts eject"
},
We're using the npm library cross-env because it works on unix and windows. The syntax is cross-env var=value command.
Now instead of import module from '../../my/module' we can do import module from 'src/my/module'
Extra details on implementation
Its important to note that cross-env's scope is limited to the command it executes, so cross-env var=val command1 && command2 will only have var set during command1. Fix this if needed by doing cross-env var=val command1 && cross-env var=val command2
create-react-app gives precedence to folders in node_modules/ over whats in NODE_PATH, which is why we're setting NODE_PATH to "." instead of "./src". Using "." requires all absolute imports to start with "src/" which means there should never be a name conflict, unless you're using some node_module called src.
(Note of caution: The solution described above replaces the variable NODE_PATH. Ideally we would append to it if it already exists. NODE_PATH is a ":" or ";" separated list of paths, depending on if its unix or windows. If anyone finds a cross-platform solution to do this, I can edit my answer.)
With webpack you can also make paths starting with for example ~ resolve to the root, so you can use import Loading from '~/components/Loading';:
resolve: {
extensions: ['.js'],
modules: [
'node_modules',
path.resolve(__dirname + '/app')
],
alias: {
['~']: path.resolve(__dirname + '/app')
}
}
The trick is using the javascript bracket syntax to assign the property.
In Webpack 3 the config is slightly diffrent:
import webpack from 'webpack';
import {resolve} from 'path';
...
module: {
loaders: [
{
test: /\.js$/,
use: ["babel-loader"]
},
{
test: /\.scss$|\.css$/,
use: ["style-loader", "css-loader", "sass-loader"]
}
]
},
resolve: {
extensions: [".js"],
alias: {
["~"]: resolve(__dirname, "src")
}
},
If you're using Create React App you can add paths.appSrc to resolve.modules in config/webpack.config.dev.js and config/webpack.config.prod.js.
From:
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(...
To:
resolve: {
modules: [paths.appSrc, 'node_modules', paths.appNodeModules].concat(...
Your code would then work:
import Loading from 'components/Loading';