Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ES modules in the externals configuration option #8895

Closed
philipwalton opened this issue Mar 11, 2019 · 21 comments
Closed

Add support for ES modules in the externals configuration option #8895

philipwalton opened this issue Mar 11, 2019 · 21 comments
Projects

Comments

@philipwalton
Copy link
Contributor

@philipwalton philipwalton commented Mar 11, 2019

Feature request

What is the expected behavior?

Webpack currently supports the concept of externals but it only allows the forms: root, commonjs, commonjs2, and amd.

This is a problem if my source code depends on a third-party library that is hosted on a CDN (in ES module format).

For example, the following does not currently work with webpack:

import {externalLibrary} from 'https://cdn.com/externalLibrary.mjs';

// Do something with `externalLibrary`.
externalLibrary.init();

What is the motivation or use case for adding/changing the behavior?

Now that most browsers natively support loading modules via import statements in module scripts, it should be possible to leverage this feature while still bundling with webpack.

Webpack already has the notion of external dependencies, it should be possible to do the same with external module dependencies.

How should this be implemented in your opinion?

External imports could be placed at the top of each chunk, and their exported variables could be renamed as necessary to ensure no collisions with existing chunk code.

Are you willing to work on this yourself?

I'm willing to help, but cannot commit to full implementation. I looked into whether it would be possible to implement this via a plugin, and it didn't seem like it's currently possible.

@brion-fuller
Copy link

@brion-fuller brion-fuller commented Apr 8, 2019

Any update for this? I would be interested in helping on this task but would like to have an approval beforehand.

Loading

@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Apr 8, 2019

It is open source, so you can send a PR 👍

Loading

@sokra sokra added this to Maybe in webpack 5 May 13, 2019
@sokra sokra moved this from Maybe to Probably in webpack 5 Dec 15, 2019
@antonioaltamura
Copy link

@antonioaltamura antonioaltamura commented Jun 6, 2020

Are there any news about this?

Loading

@sokra
Copy link
Member

@sokra sokra commented Jun 7, 2020

Work In progress in webpack 5

So far there is a import external type for import().

There will also be a module external type in future which will work together with libraryTarget: module and generates normal imports.

Loading

@antonioaltamura
Copy link

@antonioaltamura antonioaltamura commented Oct 19, 2020

Has this been addressed in the current release?

Loading

@marcogrcr
Copy link

@marcogrcr marcogrcr commented Oct 20, 2020

@antonioaltamura:

From the blogpost:

Webpack 5 adds additional external types to cover more applications:

import: Native import() is used to load the specified request. The external module is an async module.

module: Not implemented yet, but planned to load modules via import x from "...".

So from the looks of it, you can do:

import("https://www.example.com/module.mjs").then(module => { /* (...) */ });

But you can't yet do:

import { /* (...) */ } from "https://www.example.com/module.mjs";

This seems to be consistent with what @sokra reported earlier.

Loading

@sokra
Copy link
Member

@sokra sokra commented Oct 20, 2020

Try to set externalsPresets: { web: false, webAsync: true } and import() and import, but not require() of http(s) urls should work (kind of). I haven't tried, but feel free to try.

Loading

@marcogrcr
Copy link

@marcogrcr marcogrcr commented Oct 20, 2020

@sokra:

Your comment echoes the following from the blogpost:

Some combinations and features are not yet implemented and will result in errors. They are preparations for future features. Examples:

"web" will lead to http(s): imports being treated as module externals, which are not implemented yet (Workaround: externalsPresets: { web: false, webAsync: true }, which will use import() instead).


Out of curiosity I decided to give it a try, and here are my findings:

1. npx webpack will fail to bundle with the following error when { target: "web" } (either implicitly or explicitly):

The target environment doesn't support 'import()' so it's not possible to use external type 'import'

This happens even when using externalsPresets: { web: false, webAsync: true }.

2. npx webpack will succeed to bundle when { target: "es2020" } (which makes sense since import() was introduced in es2020).

3. npx webpack will succeed to bundle when { target: "browserslist" } (either implicitly or explicitly) when the target browsers support import() (e.g. last 2 Chrome versions, last 2 Firefox versions, Firefox ESR).

4. npx webpack-dev-server currently fails with:

Cannot find module 'webpack-cli/bin/config-yargs'
Require stack:
- /Volumes/Unix/webpack-5-test/node_modules/webpack-dev-server/bin/webpack-dev-server.js`

package.json:

{
  "name": "webpack-5-test",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "webpack": "5.1.3",
    "webpack-cli": "4.1.0",
    "webpack-dev-server": "3.11.0"
  }
}

webpack.config.js

module.exports = {
  externalsPresets: {
    web: false,
    webAsync: true
  }
};

src/index.js

// static
import { add, VALUE_1, VALUE_2 } from "http://localhost:8000/mod.mjs";
console.log("static:", add(VALUE_1, VALUE_2));

// dynamic
import("http://localhost:8000/mod.mjs")
  .then(({ add, VALUE_1, VALUE_2 }) => console.log("dynamic:", add(VALUE_1, VALUE_2)));

dist/mod.mjs

export const VALUE_1 = 1;
export const VALUE_2 = 2;

export function add(param1, param2) {
  return param1 + param2;
}

dist/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack 5 test</title>
  </head>
  <body>
    <script type="module" src="/main.js"></script>
  </body>
</html>

Loading

@marcogrcr
Copy link

@marcogrcr marcogrcr commented Oct 20, 2020

And outstanding question that I have is whether webpack 5 is capable of bundling a series of .js files into a single .mjs file. For example, if the file dist/mod.mjs in the previous comment imported another module, it could create a single output file dist/mod.mjs that bundled the two modules and emitted the exports suitable for ES modules.

I tried using the following without success:

Attempt 1: Generates a non ES module output.

module.exports = {
  externalsPresets: {
    web: false,
    webAsync: true
  },
  entry: {
    mod: "./src/mod.mjs"
  },
  experiments: {
    outputModule: true
  }
};

Attempt 2: Fails with a Terser error: 'return' outside of function [webpack/startup:4,0][mod.js:65,9]

module.exports = {
  externalsPresets: {
    web: false,
    webAsync: true
  },
  entry: {
    mod: "./src/mod.mjs"
  },
  experiments: {
    outputModule: true
  },
  output: {
    // this is supposed to be set by `experiments.outputModule = true`
    iife: false,
    libraryTarget: "module",
    scriptType: "module",
    module: true,
  },
};

Edit:

@marcogrcr Can you provide example of second attempt?

@evilebottnawi For both attempt 1 and attempt 2, I have just used the code from this comment. The only difference is that the file mod.mjs is in src/ rather than dist/ and webpack.config.js has the contents shown in this comment.

Loading

@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Oct 21, 2020

@marcogrcr Can you provide example of second attempt?

Loading

@sokra
Copy link
Member

@sokra sokra commented Oct 21, 2020

The error is caused because libraryTarget: module is not implemented yet and generated weird code. You can't export esm yet. But when omitting this option you should be able to consume esm as external

Loading

@antonioaltamura
Copy link

@antonioaltamura antonioaltamura commented Oct 21, 2020

Is there a plan to implement libraryTarget: module? That's what I actually need: consume a module built in webpack as a library in another project (built in webpack, of course), keeping the first one as an external dependency.

Loading

@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Oct 21, 2020

@antonioaltamura

Is there a plan to implement libraryTarget: module?

Yes, we have issue for this, on top our roadmap, after fixing critical regressions (after stable v5 release)

Loading

@davidclark87
Copy link

@davidclark87 davidclark87 commented Dec 3, 2020

@antonioaltamura

Is there a plan to implement libraryTarget: module?

Yes, we have issue for this, on top our roadmap, after fixing critical regressions (after stable v5 release)

@alexander-akait is this the relevant issue to watch? #2933

Loading

@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Dec 4, 2020

And yes and no

Loading

@vankop
Copy link
Member

@vankop vankop commented Sep 15, 2021

@sokra is it still an issue?

Loading

@rodoabad
Copy link

@rodoabad rodoabad commented Nov 2, 2021

@vankop this should still be an issue. I didn't see any clear solution on how to consume external ESM packages in this issue thread.

Loading

@vankop
Copy link
Member

@vankop vankop commented Nov 23, 2021

This feature supported in experiments.buildHttp webpack/webpack.js.org#5274 . Feel free to feedback

Loading

@vankop vankop closed this Nov 23, 2021
@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Nov 23, 2021

@vankop I don't think experiments.buildHttp is related to this, it about allowing to use import with external deps, but we support it https://webpack.js.org/configuration/externals/#externalstypemodule

Loading

@vankop
Copy link
Member

@vankop vankop commented Nov 23, 2021

hm.. yes to do something like =>

in code

import('lodash').then(() => {})

in bundle

import('https://c.dn/lodash'/* it will look another way, but do the same */).then(() => {})

you need:
webpack.confg

const path = require("path")

module.exports = [
    {
        externals: {
            lodash: 'script https://code.lodash.com/lodash-2.2.4.min.js'
        },
    }
];

Loading

@alexander-akait
Copy link
Member

@alexander-akait alexander-akait commented Nov 23, 2021

It is just script 😄

Current issue about having import {externalLibrary} from 'https://cdn.com/externalLibrary.mjs'; inside bundled code, but as I described above, we have it https://webpack.js.org/configuration/externals/#externalstypemodule

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
webpack 5
Probably
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
10 participants