Include full file path in esbuild compiled output - javascript

I want to get this file as a es module in my browser.
HelloApp.tsx
import React from "react";
import { render } from "react-dom";
function HelloApp(props: any): React.ReactElement {
return <div>Greetings</div>
}
render(
<HelloApp />,
document.getElementById("app")
);
I'm running npx esbuild ./**/*.tsx --outdir=esbuilt --format=esm
Output:
import React from "react";
import {render} from "react-dom";
function HelloApp(props) {
return /* #__PURE__ */ React.createElement("div", null, "I'm a component");
}
render(/* #__PURE__ */ React.createElement(HelloApp, null), document.getElementById("app"));
These imports aren't usable by the browser. Ideally, the path would be something like import React from "/scripts/react.js"
How do you tell esbuild to write imports like that?

The imports cannot be resolved as they are not included in the bundle. This should work if you add the --bundle argument. Also, you should use a specific file as the entry point for the bundle instead of passing a glob pattern.
Example:
npx esbuild src/app.tsx --bundle --outdir=esbuilt --format=esm
When generating a bundle, instead of --outdir you can use the --outfile argument to define the bundle file name. You probably also want to minify the bundle for the browser with the --minify argument, specify the target environment for the browsers you want to support with --target and you might want to add sourcemap support with --sourcemap.
The build command becomes:
npx esbuild src/app.tsx --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16 --outfile=esbuilt/app.js --format=esm

Related

Trying to remove 'react-hot-loader' from final bundle using webpack ignorePlugin

This is what the react-hot-loader DOCs says:
https://www.npmjs.com/package/react-hot-loader
Note: You can safely install react-hot-loader as a regular dependency instead of a dev dependency as it automatically ensures it is not executed in production and the footprint is minimal.
Even though it says that. My goals are:
I want to remove react-hot-loader from my production bundle.
And I also want a single App.js file. That should work for DEV and PROD.
The only command that I have related to react-hot-loader is inside of my App.js file:
App.js
import { hot } from 'react-hot-loader/root';
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
return(
<Layout/>
);
}
export default process.env = hot(App);
If I run it just like this, I end up with the following line on my app.js transpiled and bundled file:
/* WEBPACK VAR INJECTION /(function(process) {/ harmony import / var react_hot_loader_root__WEBPACK_IMPORTED_MODULE_0__ = webpack_require(/! react-hot-loader/root */ "wSuE");
That's expected.
But if I change my App.js file to:
AppV2.js
import { hot } from 'react-hot-loader/root'; // KEEPING THE IMPORT
import React from 'react';
import Layout from './Layout/Layout';
function App() {
console.log('Rendering App...');
console.log(window);
return(
<Layout/>
);
}
// export default hot(App); <--- COMMENTED OUT THE hot() LINE
export default App;
And I add this line to my webpack.config.js
webpack.config.js
plugins:[
new webpack.IgnorePlugin(/react-hot-loader/)
]
I'll end up with a new transpiled app.js file with this line:
*** !(function webpackMissingModule() { var e = new Error("Cannot find module 'react-hot-loader/root'"); e.code = 'MODULE_NOT_FOUND'; throw e; }());
Note: The first '***' chars in the line above don't really exist. I had to add them in order to the ! exclation mark to be shown in the quote. Don't know why but you can't start a quote with an exclamation mark.
QUESTION
Isn't the IgnorePlugin supposed to completely ignore the react-hot-loader package? Why is it being marked as missing? See that it's not even being used on the code (since I've commented out the hot() call).
Ignore Plugin only excludes that particular module in bundle generation. However it will not remove the references to the module from your source code. Hence your webpack output is throwing that error.
One way of bypassing this error is to use the DefinePlugin to create a dummy stub for react-hot-loader. More on that here.
That said react-hot-loader itself proxies the children without any changes if the NODE_ENV is production. Check here and here. So in production mode apart from the hot() function call which directly returns your component, there is no other stuff that happens.
Another option could be:
// App.js
export default function AppFactory() {
if (process.env.NODE_ENV === "development") {
return hot(App);
} else {
return App;
}
}
// index.js:
import AppFactory from './App';
const App = AppFactory();
// ...
<App />
Now since webpack is creating bundles at build time, it knows if the mode is development or production (more on build modes) and should be able to eliminate the dead code with tree shaking and UglifyjsWebpackPlugin.
Make sure that if you are using Babel it's not transpiling your code to CommonJS - see Conclusion section, point 2 of the tree shaking page.
Pass the ambient mode to babel.
"scripts": {
"build-dev": "webpack --node-env development",
"build-prod": "webpack --node-env production",
},

NPM package: best practices and exposing multiple import paths

I created an NPM package that uses Webpack and Babel for transpiling/bundling.
In my package.json, I've got main set to "main": "build/index.js". And in my Webpack config, I have entry set to entry: { app: './src/index.js' }. My entry file is shown below.
Everything works fine when the package is installed. However, with this setup, two import paths are exposed for every helper:
This is a problem for editors that support auto imports, since they will sometimes auto import from 'my-package/build/utils/helper1' rather than the preferred path of 'my-package'.
So, two questions:
Is there any way to prevent the longer import path from being exposed?
What is considered best practice when creating NPM packages. Is my setup acceptable, or should I be doing something different?
Entry File:
import helper1 from './utils/helper1';
import helper2 from './utils/helper2';
export {
helper1,
helper2,
};
const myPackage = {
helper1,
helper2,
};
export default myPackage;
you can utilize Webpack resolve
I often use the first way:
export {
helper1,
helper2,
};
Recently, I found that we can use Object.freeze() to export. This is a good article.
I would suggest probably merge your helper1 and helper2 in one file and name it helpers, then you can put them in the class myPackage so that you then export them as a module like this
import myPackage from './utils/helper';
// OR import {helper1, helper2} from './utils/helpers';
export default class myPackage {
helper1,
helper2,
};
OR
import {myPackage} from './utils/helpers';
// OR import {helper1, helper2} from './utils/helpers';
module.exports.myPackage = (helper1, helper2) => {
this.helper1 = helper1;
this.helper2 = helper2;
};
I hope this helps.

How to make a class-based custom element side-effect-free so webpack only bundles the explicitly imported components

I have a set of spec v1 custom elements which I'm using webpack 4 to bundle (and babel-loader to transpile).
The components all look similar to this:
export class CompDiv extends HTMLDivElement {
constructor(...args) {
const self = super(...args);
self.property = null;
return self;
}
connectedCallback() {
console.log('connected CompDiv');
}
}
customElements.define('comp-div', CompDiv, { extends: 'div' });
Now to be able to create custom packages from these components using selective, named imports I need to mark these files as side-effect-free.
The component registration, though, takes place in the module itself:
customElements.define('comp-div', CompDiv, { extends: 'div' });
As far as I understand, that is a sideeffect.
Now I have an index.js that basically looks like this:
export { CompDiv } from './components/comp-div/comp-div';
...
export { CompBtn } from './components/comp-btn/comp-btn';
My webpack entry point looks like this:
import 'document-register-element';
import 'babel-polyfill';
import { CompDiv } from './index';
Now when I do this, CompBtn (and all other exports in index.js) ends up being part of the bundle even though it's not imported in my webpack entry point.
What would be the recommended way of allowing for treeshaking in webpack with these web components?
From webpack guide - Mark the file as side-effect-free:
All the code noted above does not contain side effects, so we can simply mark the property as false to inform webpack that it can safely prune unused exports.
So, setting "sideEffects": false in package.json tells webpack that your modules are side effect free. So that it can prune unused exports (in your case, unused re-exports). This is generally used by library authors.
But that's just one side of the equation.
From webpack configuration docs - optimization.sideEffects:
Tells webpack to recognise the sideEffects flag in package.json or rules to skip over modules which are flagged to contain no side effects when exports are not used.
So, in order to leverage that previously mentioned option, the library consumer will have to set the optimization.sideEffects option to true in their webpack config file:
// webpack.config.js
module.exports = {
...
optimization: {
sideEffects: true
}
...
}
Note that, in production mode, this option is enabled by default. So, you'll only need to set it for development mode.
N.B.: In this case, you are both the author and the consumer of your modules.
Lastly, let's look at your webpack entrypoint:
// webpack entrypoint
import 'document-register-element';
import 'babel-polyfill';
import { CompDiv } from './index';
If you don't use your imported CompDiv later in this file, webpack will prune it - assuming you've set "sideEffects": false in package.json and optimization.sideEffects to true in your webpack config.
But, for example, even if you only imported 'babel-polyfill' and won't explicitly use anything from it later in this file, webpack will not prune it, because the package.json for babel-polyfill library doesn't contain "sideEffects": false.
I hope that clears things up.

React, webpack and creating a bundle using a per build json?

We would like to create bundle.js from React and webpack, where a per build configuration is included and made available to the React code, but we are not sure how to go about doing this.
The idea would be to be to do something like:
npm run build -- config="config123.json"
And then have the generated bundle.js include that configuration, such that it could be used by the root container. Something like:
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
ReactDOM.render(
<RootContainer config={configPassedByBuildProcess}/>,
document.getElementById('app')
);
Is this possible and if so, how should we approach this?
No, I don't believe that you can pass a file on build, from what I know the only thing that you can pass is a string like an ENV variable. You could import the config123.json file like this
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
import config from '/path/to/config123.json'
ReactDOM.render(
<RootContainer config={config}/>,
document.getElementById('app')
);
You may need a JSON loader for importing JSON files
After a bit of experimenting, the solution I have found is to use Webpack's DefinePlugin, since this provides a way of doing string substitution during webpack's build phase.
In my React code I defined:
// Will be substituted by webpack's DefinePlugin. Not an error.
const bundleConfig = BUNDLE_CONFIG;
and then in my webpack.config.js I first load my config (hard coded for now):
// The path can be pulled in via 'process.args' and parsed as appropriate
const bundleConfig = fs.readFileSync('bundle-config/default-config.json', 'UTF-8');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Note that if for some reason we want to build with a missing or undefined bundleConfig, then the right substitution would likely be the string (not the keyword) 'undefined' , to keep the code valid, such that:
const bundleConfig = 'undefined';
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Just one extra thing was the configuration of the package.json (scripts section only):
{
"scripts": {
"build": "run() { NODE_ENV=production && webpack -p $1; }; run",
}
}
This allow passing in the path to the webpack.conf.js script, when we call:
npm run build /path/to/config.json

Create react app with browserify has error

I am learning React and try to create a simple React application.
I want to use ES2015 modules and some ES6 features so I installed Babel and browserify via npm.
These are node modules that I installed:
babel
babel-preset-es2015
babel-preset-react
babelify
browserify
gulp
reactify
vinyl-buffer
vinyl-source-stream
react
react-dom
I want to make script into several files (like itemComponent as React) and merge them into dist/app.js that actually website loads. For this, I used gulp!
This is my gulpfile.js:
var source = require('vinyl-source-stream');
var gulp = require('gulp');
var browserify = require('browserify');
var reactify = require('reactify');
var babelify = require('babelify');
gulp.task('default', function() {
return browserify('./source/' + file)
.transform(babelify, { presets: ["es2015", "react"] })
.bundle()
.pipe(source(file))
.pipe(gulp.dest('./dist'));
});
Gulp is actually working well, everything is transpiled good without error. But check the website, it says:
app.js:19474 Uncaught ReferenceError: React is not defined
So I removed react and react-dom installed via npm, and just download React and load from HTML simply just use script tag, and it works.
But I want to include react like this:
import React from 'react';
But it is not working. This is my app.js:
import React from 'react';
import ReactDOM from 'react-dom';
import ItemList from './components/itemlist';
let items = [ ... ];
class App extends React.Component {
render() {
return (
<div>
<h1>List of items:</h1>
<ItemList items={this.props.items} />
</div>
)
}
}
ReactDOM.render(<App items={items} />, document.getElementById('app'));
I don't know what is the problem and I don't want to load each react file using script tag. Is there something I missed?
Reason of this issue were I forgot to import React module in child components, .
JSX will transpile to something like React.createElement, but there wasn't React exists so that's why I have Reference Error.

Categories