I have found a few StackOverflow questions related to this but none that match nor fix my problem.
I am writing a library in ES6 that is intended to be used in the browser and on the server. I have found a few HTTP request libraries that can be use on the server or browser (popsicle, axios). I have successfully used these libraries in both places but am having some issues when importing them in my source and using the outputted webpacked file.
My ES6 source file where I am importing the axios library is
import axios from 'axios';
export default {
go: function() {
return axios.get('http://www.google.com');
}
};
My webpack config is
var webpack = require('webpack');
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
var WebpackNotifierPlugin = require('webpack-notifier');
var path = require('path');
var env = require('yargs').argv.mode;
var libraryName = 'library';
var source = '/src/test.js';
var plugins = [],
outputFile;
if (env === 'build') {
plugins.push(new UglifyJsPlugin({
minimize: true
}));
outputFile = libraryName + '.min.js';
} else {
outputFile = libraryName + '.js';
plugins.push(new WebpackNotifierPlugin())
}
var config = {
entry: __dirname + source,
devtool: 'source-map',
output: {
path: __dirname + '/lib',
filename: outputFile,
library: libraryName,
libraryTarget: 'umd',
umdNamedDefine: true
},
externals: {},
module: {
loaders: [{
test: /(\.jsx|\.js)$/,
loader: 'babel',
exclude: /(node_modules|bower_components)/
}, {
test: /(\.jsx|\.js)$/,
loader: "eslint-loader",
exclude: /node_modules/
}]
},
resolve: {
root: path.resolve('./src'),
extensions: ['', '.js']
},
plugins: plugins
};
module.exports = config;
As you can see I am setting the libraryTarget to umd and umdNamedDefine to true
And my .bablerc is
{
"presets": ["es2015"],
"plugins": ["add-module-exports", "babel-plugin-add-module-exports"]
}
I am able use my library in the browser by including it in a script tag but I am not able to use it when importing with node. I get an XMLHttpRequest is not defined. The axios library says it uses XMLHttpRequest when on the browser and http when running in node but for some reason it is not detecting it is being run on the server. I am having this issues with a few UML libraries so believe it is not the specific package. Also, since I can use these libraries in node ES5 without running webpack or babel I am led to assume it is a configuration issue with webpack.
How can I import these UMD style libraries in ES6 and generate a library with Webpack and Babel that can be used on the server and browser?
To make your package as small as possible I'd recommend using the Fetch API. A UMD library has the three types of consumers where fetch comes in handy;
Node.js - has not implemented but can use node-fetch to polyfill
common behaviour using only node libraries (no heavy dependencies
like superagent, unirest and axios etc - these add security concerns
as well as bloat!).
Browser - Fetch is a WHATWG standard and is
available in most modern browsers but might require an npm package
such as whatwg-fetch to polyfill older browsers
Isomorphic/Universal - the same javascript running in browser and
node.js which you find in progressive web apps.They need to use a
library called isomorphic-fetch to load either whatwg-fetch or the
node.js version of fetch.
It should be handled by the projects consumer though so README should include instructions to each of the three types of users above.
Node.js and isomorphic instructions are basically as below.
require(‘node-fetch’); // or require(‘isomorphic-fetch’)
const MyUMDLibrary = require('my-umd-library');
const myUMDLibrary = new MyUMDLibrary();
For browsers using the script from cdn they could also load a fetch polyfill before https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.js.
I'd suggest using rollup for a UMD library (see a working example I've contributed before for a UMD library) but Webpack shouldn't be a problem either. Rather than trying to include the 'axios' dependency within your application with Fetch you can leave it out and allow your users to decide how they will load the package (i.e. module loader, script, require).
Related
I'm next js for my project and it uses webpack 5 to compile typescript codes
I have several web worker scripts inside my public folder under path "/workers/**/*.worker.js"
I was wondering if I can write them in typescript too
or at least use babel to transpile them for es5 (for old browsers)
I know that anything under the "public" folder is served as is and as a file (like a CDN)
can I add a "workers" folder to my project and load them in the public path with webpack and next js?
thanks to #nalin-ranjan I've come up with the solution
in my "next.config.js" I added a rule to my webpack config:
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.worker\.ts$/,
type: 'asset/resource',
generator: {
filename: 'static/[hash:5].[name].js',
},
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true,
configFile: __dirname + '/worker.tsconfig.json',
},
},
],
})
return config
},
}
with this rule, I could require my workers and use them as URLs and transpile typescript
also, I had to add a new "tsconfig" that had "isolatedModules" option disabled (because web workers are not modules). for that, I created an identical tsconfig file to next.js but disabled isolatedModules. the reason being that next.js forbids you to disable isolatedModules and resets it back
I am trying to set up the following architecture: a core React application that gets built with some basic functionality, and the ability to load additional React components at runtime. These additional React components can be loaded on-demand, and they are not available at build time for the core application (so they cannot be included in the bundles for the core application, and must be built separately). After researching for some time, I came across Webpack Externals, which seemed like a good fit. I am now building my modules separately using the following webpack.config.js:
const path = require('path');
const fs = require('fs');
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
entry: './src/MyModule.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'MyModule.js',
library: 'MyModule',
libraryTarget: 'umd'
},
externals: {
"react": "react",
"semantic-ui-react": "semantic-ui-react"
},
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
include: resolveApp('src'),
loader: require.resolve('babel-loader'),
options: {
compact: true,
},
}
]
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
}
};
Took a look at the generated MyModule.js file, and it looks correct to me.
Now, in my core app, I am importing the module as follows:
let myComponent = React.lazy(() => import(componentName + '.js'));
where componentName is the variable that matches the name of my module, in this case, "MyModule" The name is not known at build time, and the file is not present in the src folder at build time. To avoid errors from webpack when building this code with an unknown import, I have added the following to my webpack.config.js for the core project:
module.exports = {
externals: function (context, request, callback/*(err, result)*/) {
if (request === './MyModule.js') {
callback(null, "umd " + request);
} else {
callback();
}
}
}
I have confirmed that the function in externals gets called during the build, and the if condition is matched for this module. The build succeeds, and I am able to run my core application.
Then, to test dynamic loading, I drop MyModule.js into the static/js folder where the bundles for my core app live, then I navigate to the page in my core app that requests MyModule via let myComponent = React.lazy(() => import(componentName + '.js'));
I see a runtime error in the console on the import line,
TypeError: undefined is not a function
at Array.map (<anonymous>)
at webpackAsyncContext
My guess is it's failing to find the module. I don't understand where it is looking for the module, or how to get more information to debug.
Turns out that I was making a couple of incorrect assumptions about webpack and dynamic loading.
I was having issues with two things - the kind of module I was loading, and the way that I was loading it.
Dynamic importing is not yet a standard ES feature - it is due to be standardized in ES 2020. This dynamic import will only return a module if the module object you are attempting to load is an ES6 module (aka something that contains an 'export ModuleName'). If you attempt to load something packed up as a CommonJS module, AMD, UMD, the import will succeed, but you will get an empty object. Webpack does not appear to support creating bundles in ES6 format - it can create a variety of module types, and in my config file above, I was actually creating UMD modules (configured via libraryTarget setting).
I had issues with the import statement itself because I was using it within an app bundled by Webpack. Webpack reinterprets the standard ES import statement. Within a standard webpack config (including the one you get from CRA), webpack uses this statement as a split point for bundles, so even modules that are dynamically imported are expected to be there at webpack build time (and the build process will fail if they are not available). I had tried to use webpack externals to tell webpack to load the modules dynamically, which allowed the build to succeed without the modules being there. However, the app still used Webpack's import function instead of the standard JS import function at runtime. I confirmed this by attempting to run import('modulename') from the browser console and getting a different result than my app, which was bundled with webpack.
To solve problem #2, you can tell Webpack to not reinterpret the ES dynamic import by adding some annotation to the import statement.
import(/*webpackIgnore: true*/ 'path/to/module.js');
This will both prevent Webpack from attempting to find and bundle the dynamically imported module at build time, and attempting to import it at runtime. This will make behavior in the app match behavior in the browser console.
Problem #1 was a bit more difficult to solve. As I mentioned above, importing a non-ES6 module will return an empty object (if you await the promise or use .then()). However, as it turns out, the file itself does load and the code gets executed. You can export the module in the "window" format using Webpack, and then load it as follows.
await import(/*webpackIgnore: true*/`path/to/module.js`);
let myModule = window['module'].default;
Another potential solution that avoids using the window object is building the module using a system capable of producing ES6 modules (so, not Webpack). I ended up using Rollup to create an ES6 module that pulled all dependencies into a single file, and ran the output through Babel. This produced a module that loaded successfully via a dynamic ES import. The following was my rollup.config.js (note that I included all external node modules needed in my module - this bloated the module size but is a requirement for my specific application - yours will likely differ and you will need to configure rollup to exclude the modules)
// node-resolve will resolve all the node dependencies
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';
export default {
input: 'src/myModule.jsx',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
}),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/react/index.js': ['Children', 'Component', 'PropTypes', 'PureComponent', 'React', 'createElement', 'createRef', 'isValidElement', 'cloneElement', 'Fragment'],
'node_modules/react-dom/index.js': ['render', 'createElement', 'findDOMNode', 'createPortal'],
'node_modules/react-is/index.js': ['isForwardRef']
}
}),
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
})
]
}
I've written a library in es6 using import/export. I can bundle this library using Rollup into a IIFE that can be used in the browser.
I also want to be able to use this library in other projects. However, I won't usually want to include the whole library, only parts of it.
Because the library is written using es6 import/export I can include the unbundled index.js file as a dependency in another project and then import { myFunc } from 'my-lib' and this works great - I only get myFunc when my project is bundled.
However, I ran into an issue because these files haven't been processed by babel and therefore contain es6 code such as arrow functions. I have read that if you're going to publish a library it shouldn't need to be transpiled by the end-user.
How can I take my es6 library and bundle it in such a way that it is transpiled, but also able to have its individual components imported? I want a similar situation to how lodash is organised.
This isn't the easiest thing to articulate so if anything isn't clear please leave a comment and I'll edit my question to try and clarify.
I have found it is best to use webpack to create 2 modules. One for web and one for node.
In your node build, you can choose to transpile, or simply keep the code in ES6, letting the library client decide.
Your web build can tree-shake and remove unused code.
Your webpack will export an array instead of an object.
Here is the official webpack snippet (https://webpack.js.org/concepts/targets/)
const path = require('path');
const serverConfig = {
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.node.js'
}
//…
};
const clientConfig = {
target: 'web', // <=== can be omitted as default is 'web'
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'lib.js'
}
//…
};
module.exports = [ serverConfig, clientConfig ];
I'm migrating a web app from requireJS to webpack.
With requireJS, I have different configurations depending on the environment.
For live environment I use r.js to minify and bundle all of my
modules and their dependencies into a single file. Afterwards, I add
almondJS to manage the dependencies and then I load my js bundle like the following:
<script src="bundle.min.js"></script>
For my development environment, I Load requireJS like this:
<script src="require.js" data-main="/main-config"></script>
and requireJS will use my configuration file specified by data-main, to load modules and their
dependencies asynchronously
As you can see, with requireJS module loading and bundling are two separate processes and that allows me to debug AMD modules during development without needing sourcemaps
How can I achieve this scenario using webpack as a module loader only without bundling during development ?
If this is not possible, is there any other way I can see my source files in the browser debugger without generating sourcemaps?
How can I achieve this scenario using webpack as a module loader only without bundling during development ?
Webpack will always bundle, despite the envieronment.
If this is not possible, is there any other way I can see my source files in the browser debugger without generating sourcemaps?
If your code is transpiled/compiled, you'll need sourcemaps to see that. There is no way to workaround that.
It's true that if your code is transpiled then you'll need sourcemaps. But it is possible to get around bundling though. Yes, webpack will really always try to bundle, but with plugins the code can be taken out of the bundle and placed in the output directory as if it was simply run through the transpiler.
I have a node application that I want to simply transpile to ES5 file-by-file and not bundle anything. So my config to do that is roughly this:
let config = {
entry: [
glob.sync(srcDir + '/**/*.js') // get all .js files from the source dir
],
output : {
filename : '[name].rem.js', // webpack wants to bundle - it can bundle here ;)
path: outDir
},
resolve: {
alias: {
'app': appDir
}
},
plugins: [
new RemoveEmptyScriptsPlugin({extensions: ['js'], scriptExtensions: /\.rem\.js/}) // for all .js source files that get bundled remove the bundle .rem.js file
],
module: {
rules:[{
test: /\.jsx?$/,
type: 'asset/resource', // get webpack to take it out instead of bundling
generator: {
filename: ({filename}) => filename // return full file name so directory structure is preserved
},
use: {
loader: 'babel-loader',
options: {
targets: { node: 16 },
presets: [
['#babel/preset-env', { modules: 'commonjs' /* transpile import/export */}],
]
}
}
}]
}
};
// Since the code does not go through the full pipeline and imports are not getting resolved, aliases will remain in the code.
// To resolve them it takes to hack the aliases object into the babel config
config.module.rules[0].use.options.plugins.push(['babel-plugin-webpack-alias-7', {config: {resolve: {alias: config.resolve.alias}}}];
But then it appeared that the currently published babel-plugin-webpack-alias-7 does not support providing an Object to the config option so I had to patch the plugin https://github.com/shortminds/babel-plugin-webpack-alias-7/pull/22
Ah, and then the webpack-remove-empty-scripts plugin had an issue with my idea so I had to patch that too https://github.com/webdiscus/webpack-remove-empty-scripts/pull/6
When this code (generated from babel) runs I get an error exports is undefined
Object.defineProperty(exports, '__esModule', {
any ideas?
You are most likely not executing the code in an environment that supports CommonJS modules. You could use a bundler, such as Browserify or webpack
to bundle your modules into something that can be run in different environments.
Or you could choose a different module transformer.
With webpack
Run npm install -g webpack; npm install -D babel-loader. Then with this webpack configuration:
// webpack.config.js
module.exports = {
entry: "./path/to/entry/module.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
]
}
};
running the webpack command will convert all *.js files reachable via the entry file with babel and bundle them together into bundle.js.
I read an article about how ES6 import and export are only supposed to work in browser with "statically analyzable files" and Babel removed import and export support in the browser because of this. Something to do with async or possibly security?
If you want to skip the server-side bundling for dev purposes, you can put
window.MyModule = MyModule at the bottom, then import
var MyModule = window.MyModule at the top of your next file