Will webpack produce different results given a configuration like so:
// webpack.config.js
module.exports = {
...
entry: {
main: ['./index.js'],
}
}
// index.js
import 'babel-polyfill'
...
vs.
// webpack.config.js
module.exports = {
...
entry: {
main: ['babel-polyfill', './index.js'],
}
}
// index.js
// babel-polyfill import removed
...
Which one is preferred, and why?
Both works kind of the same way.
The option 1, webpack would treat babel-polyfill as a dependency, in the dependency tree.
The second one, webpack would treat babel-polyfill as an entrypoint, where it would try to generate a dependency graph from that, which would have 0 dependencies.
There is no real difference here, nor any impact on the result bundle, both will contain babel-polyfill anyways, and also there is no "preferred" way to add that, babel itself refeers to both ways on their guide.
The result will be the same. It depends on you, usually I prefer to import the dependencies in modules rather than importing implicitly in webpack config.
Related
I have project which uses lerna ( monorepo, multiple packages ). Few of the packages are standalone apps.
What I want to achieve is having aliases on few of the packages to have something like dependency injection. So for example I have alias #package1/backendProvider/useCheckout and in webpack in my standalone app I resolve it as ../../API/REST/useCheckout . So when I change backend provider to something else I would only change it in webpack.
Problem
Problem appears when this alias is used by some other package ( not standalone app ). For example:
Directory structure looks like this:
Project
packageA
ComponentA
packageB
API
REST
useCheckout
standalone app
ComponentA is in packageA
useCheckout is in packageB under /API/REST/useCheckout path
ComponentA uses useCheckout with alias like import useCheckout from '#packageA/backendProvider/useCheckout
Standalone app uses componentA
The error I get is that Module not found: Can't resolve '#packageA/backendProvider/useCheckout
However when same alias is used in standalone app ( that has webpack with config provided below ) it is working. Problem occurs only for dependencies.
Potential solutions
I know that one solution would be to compile each package with webpack - but that doesn't really seem friendly. What I think is doable is to tell webpack to resolve those aliases to directory paths and then to recompile it. First part ( resolving aliases ) is done.
Current code
As I'm using NextJS my webpack config looks like this:
webpack: (config, { buildId, dev, isServer, defaultLoaders }) => {
// Fixes npm packages that depend on `fs` module
config.node = {
fs: "empty"
};
const aliases = {
...
"#package1/backendProvider": "../../API/REST/"
};
Object.keys(aliases).forEach(alias => {
config.module.rules.push({
test: /\.(js|jsx)$/,
include: [path.resolve(__dirname, aliases[alias])],
use: [defaultLoaders.babel]
});
config.resolve.alias[alias] = path.resolve(__dirname, aliases[alias]);
});
return config;
}
You don’t need to use aliases. I have a similar setup, just switch to yarn (v1) workspaces which does a pretty smart trick, it adds sym link to all of your packages in the root node_modules.
This way, each package can import other packages without any issue.
In order to apply yarn workspaces with lerna:
// lerna.json
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/**"
],
}
// package.json
{
...
"private": true,
"workspaces": [
"packages/*",
]
...
}
This will enable yarn workspace with lerna.
The only think that remains to solve is to make consumer package to transpile the required package (since default configs of babel & webpack is to ignore node_module transpilation).
In Next.js project it is easy, use next-transpile-modules.
// next.config.js
const withTM = require('next-transpile-modules')(['somemodule', 'and-another']); // pass the modules you would like to see transpiled
module.exports = withTM();
In other packages that are using webpack you will need to instruct webpack to transpile your consumed packages (lets assume that they are under npm scope of #somescope/).
So for example, in order to transpile typescript, you can add additional module loader.
// webpack.config.js
{
...
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
include: /[\\/]node_modules[\\/]#somescope[\\/]/, // <-- instruct to transpile ts files from this path
options: {
allowTsInNodeModules: true, // <- this a specific option of ts-loader
transpileOnly: isDevelopment,
compilerOptions: {
module: 'commonjs',
noEmit: false,
},
},
}
]
}
...
resolve: {
symlinks: false, // <-- important
}
}
If you have css, you will need add a section for css as well.
Hope this helps.
Bonus advantage, yarn workspaces will reduce your node_modules size since it will install duplicate packages (with the same semver version) once!
I have a NPM package (private) which works in both a browser and Node environment.
This is done by creating separate bundles via Rollup for ES and CJS, so the output looks like:
dist/ejs/index.js // Import this for your browswer environments
dist/cjs/index.js // Use this for Node environments
Pretty standard. Now I'm adding a dependency to this, which follows the same bundling pattern.
I can import the library like so:
import { externalLibrary } from "#external/ejs/externalLibrary";
All is good in a browser environment. But now this does not work in a Node environment, as what I'm importing is not CJS.
I could change the way I import the library to require and target the cjs bundle:
const { externalLibrary } = require("#external/cjs/externalLibrary");
And while this works in both environments, I don't think it's optimal.
Is there a better way of doing this? Some configuration that I could specify when exporting the CJS bundle?
module.exports = {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'cjs'
// Behaviour here for #external/cjs/externalLibrary ?
}
};
I overlooked the package.json config. You can specify different entry files depending on the build here:
{
...
"main": "dist/cjs/index.js",
"module": "dist/ejs/index.js",
...
}
Then I removed the implicit import of the EJS file, and targeted just the package:
// Before:
import { externalLibrary } from "#external/dist/ejs/externalLibrary";
// After:
import { externalLibrary } from "#external";
This then ensures either the CJS or ES build is used, depending on the environment using the package.
Looks like you already found the solution for this, but even with old import style
import { externalLibrary } from "#external/dist/ejs/externalLibrary";
you should be able to target appropriate formats for cjs vs esm. With rollup, you would have to configure the output config to be an array of objects with appropriate format set. For example:
module.exports = {
input: 'src/main.js',
output: [{ file: 'dist/index.cjs.js', format: 'cjs' },
{ file: 'dist/index.esm.js', format: 'es' }],
}
Also, being an author of klap, I would recommend giving it a try as it would bring in lot of other optimizations by default.
Im using webpack 4 to bundle my dependencies (in this case AngularJS) like so:
index.js
require('angular');
require('angular-ui-router');
// ...
webpack.config.js
const path = require('path');
module.exports = {
mode: "development",
entry: "./build/index.js",
output: {
path: __dirname + '/public/js',
filename: "bundle.js"
}
}
This generates a bundle.js file containing all my dependencies.
I would additionally like to use it as a task runner to concatenate the Angular JS files from /build/js into a single file (lets call it app.js) thats placed into /public/js
Ideally, I would like the representation of the concatenated angular files (what would be in app.js) to be included within bundle.js along with my dependencies - though Im not sure if this is possible or best-practice.
As #Pete has pointed out in the comment, webpack input accepts an array of entry paths. Webpack itself doesn't take glob pattern, but you can use the glob package to do so (if you use webpack, it's likely that you've already installed it, otherwise get it here):
const glob = require('glob');
module.exports = {
mode: 'development',
entry: glob.sync('./src/**/*.js'), // ['./src/a.js', './src/dir/b.js']
...
}
Hope it helps!
Given the following code:
import { graphql } from 'graphql'
import graphqlTools from 'graphql-tools'
const { makeExecutableSchema } = graphqlTools
const typeDefs = `
type Query {
as: [A]
}
type A {
x: Int,
y: Int
}
`
const schema = makeExecutableSchema ({ typeDefs })
graphql(schema, '{ as { x, y } }').then(console.log)
I get this error:
Error: Cannot use GraphQLSchema "[object GraphQLSchema]" from another
module or realm.
Ensure that there is only one instance of "graphql" in the
node_modules directory. If different versions of "graphql" are the
dependencies of other relied on modules, use "resolutions" to ensure
only one version is installed.
What's going on?
This situation may also occur when the version of the graphql module you have installed is different from the version installed and used by graphql-tools.
I have found you can correct this by either:
Changing the version of graphql in your project's package.json file to match exactly what graphql-tools depends on in its package.json file.
Removing graphql as a dependency and just installing graphql-tools. Then you will automatically receive whatever graphql module version that graphql-tools installs (as long as you don't depend on any other packages that install another, conflicting version).
In other cases you might have the correct version, but it may be installed multiple times. You can use npm ls graphql to see all the installed versions. Try running npm dedupe to remove duplicate installations.
This happens because graphql-tools module imports graphql from its CommonJS module, while my code does it from the ES module. That is, each object in my own module comes from the ES module, while graph-tool's not.
Solution
It's as easy as importing anything from graphql importing the CommonJS module, and both objects from graphql and graphql-tools will be able to talk each together:
import graphql_ from 'graphql/index.js'
import graphqlTools from 'graphql-tools'
const { graphql } = graphql_
const { makeExecutableSchema } = graphqlTools
const typeDefs = `
type Query {
as: [A]
}
type A {
x: Int,
y: Int
}
`
const schema = makeExecutableSchema ({ typeDefs })
graphql(schema, '{ as { x, y } }').then(console.log)
My problem was both .js an .mjs graphql files are resolved due to wrong webpack configuration.
Root cause:
From TypeMapper.mjs file in graphql-compose, import statement does not have file extension and that was a failure on webpack bundle. In order to solve it, I required to add fullySpecified:false into the webpack config.
{
test: /\.m?js/,
include: /node_modules/,
type: "javascript/auto",
resolve: {
fullySpecified: false
}
}
And I also modified resolve statement like
resolve: {
extensions: [".ts", ".js", ".mjs"] // that was the actual problem
}
Since fullySpecified config has been set to false, webpack was trying to resolve files without extension respect to the order of resolve.extentions config. Due to the wrong order in that config, graphql files with .js ext were been resolving although all other files were using .mjs one.
Solution:
Simply re-order resolve.extensions config as
resolve: {
extensions: [".ts", ".mjs", ".js"]
}
In my case, I added the webpack-node-externals library to the webpack configuration, and I was able to run my bundled application.
externalsPresets: { node: true },
externals: [nodeExternals()],
I am using webpack version 5.*
Also I am using yarn package manager, so I added resolutions in my package.json
"resolutions": {
"graphql": "^15.3.0"
}
For me, it was solved by downgrading some packages like this:
"apollo": "^2.33.4", "graphql": "^15.5.0",
I also deleted node_modules and package-lock.json and installed packages with yarn instead of npm.
I got this exact error when my .npmrc did not have proper entries such as username and password. We are using jFrog to normalise package installation. .npmrc should be located at root with proper entries.
ex: .npmrc file which works
#<company-name>:registry=<registry-url>
//<artifactory-name>:_password=${PASSWORD}
//<artifactory-name>:username=${JFROG_USERNAME}
//<artifactory-name>:email=${YOUR_EMAIL}
//<artifactory-name>:always-auth=true
I need help here.
I am bundling a custom library (named for the example LibraryJS and it uses lodash as dependency.
In the webpack configuration I setup lodash as an external dependency like so:
{
externals: {
"lodash"
}
}
It works great.
But then when I want to use this library inside another project, I got the following error: "libraryjs.js:77 Uncaught ReferenceError: lodash is not defined"
which is the following line:
/***/ }),
/* 1 */
/***/ (function(module, exports) {
=> module.exports = lodash;
/***/ }),
The fact is, I am using lodash as well in my project, so it should be working.
What am I doing wrong?
By the way, I am using gulp + browserify to bundle my project and gulp + gulp-webpack to bundle the library.
Edit: I found a way to fix the error, but I really don't want to stick with this fix because it's really... ugly... See bellow:
const lodash = require('lodash')
window.lodash = lodash; // <= The fix, ugh
Thank you for the help!
I had a similar situation trying to build a library - this is the solution I found.
Goal:
Build a library that depends on Lodash without including Lodash in that library bundle.
The consumer of the library is responsible for making Lodash available to our library.
I used the Authoring Libraries section of the documentation to put together this solution.
Specifically these two sections:
Externalize Lodash
Expose the Library
There is an example project showing this configuration as well:
https://github.com/webpack/webpack/tree/master/examples/externals
Solution:
1. Bundle our library, excluding lodash
// "my-lib" package.json
{
"name": "my-lib",
"version": "0.5.0",
"main": "my-lib.js",
"peerDependencies": {
"lodash": "^4.17.5"
},
"devDependencies": {
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14"
}
}
Here's our example library that imports lodash and uses it. Note that whilst we depend on lodash, it won't be included in our bundle.
// lib-entry.js
import _ from "lodash";
export default function doubleUp(listOfNum){
// Do something using lodash
let result = _.flatMap(listOfNum, (i) => [i, i]);
return result;
}
Here's our webpack.config.js that bundles our library, but excludes lodash from the bundle:
// "my-lib" webpack.config.js
module.exports = {
entry: {
'my-lib': './lib-entry.ts',
},
output: {
filename: '[name].js',
// [1]
libraryTarget: 'umd',
/*
[2]
NOTE: until this issue is fixed: https://github.com/webpack/webpack/issues/6525
we need to define `globalObject` in Webpack 4 to correctly build a universal library
(i.e. node and browser compatible).
*/
globalObject: 'this',
},
externals: {
// [3]
'lodash': {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_',
},
},
resolve: {
extensions: ['.js'],
},
};
[1] This tells Webpack to bundle our library for different environments (so it will work when loaded via: browser scripts, Node CommonJS, AMD, and downstream Webpack projects). NOTE: This is needed otherwise the externals config in [3] will output an incorrect undefined module in your bundle.
[2] Workaround for a Webpack 4 bug (if you're in the future then check if this is still relevant and needed). Also described here: https://stackoverflow.com/a/49119917/81723
[3] From the documentation: "set the externals option to define dependencies that should be resolved in the target environment". We're telling Webpack to exclude lodash from the library bundle and that consumers of our library are responsible for providing it.
2. Use the library in a downstream application
Define an application that depends on our my-lib library:
// "my-app" package.json
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"lodash": "^4.17.5",
"my-lib": "^0.5.0"
},
"devDependencies": {
"webpack": "^4.5.0",
"webpack-cli": "^2.0.14"
}
}
Our application imports lodash into its environment (because our library needs it) and then use our library:
// app-entry.js
import _ from "lodash";
import doubleUp from "my-lib";
let result = doubleUp([1, 2, 3]);
console.log(result); // [1, 1, 2, 2, 3, 3]
Here's the webpack.config.js for our application:
// "my-app" webpack.config.js
const path = require('path');
module.exports = {
entry: {
'my-app': './app-entry.ts',
},
output: {
filename: '[name].js',
},
resolve: {
extensions: ['.js'],
alias: {
/*
[1]
Resolve all `lodash` requests to the same location otherwise
we end up with two versions loaded into the bundle, one
for `my-lib` and one for `my-app`.
*/
lodash: path.resolve('./node_modules/lodash/index.js'),
}
},
};
[1] This alias config means anytime Webpack encounters require('lodash') or import ... from "lodash"; it will resolve it to this one module on disk. I found I had to do this to avoid getting multiple versions of lodash loaded into my bundle. Webpack was including one version for my-app and one for my-lib, but this fixes it so that both get the same version.