Giter Site home page Giter Site logo

scriptedalchemy / webpack-external-import Goto Github PK

View Code? Open in Web Editor NEW
416.0 12.0 46.0 4.75 MB

Dynamically import modules from other webpack bundles. Painless code sharing between separate apps

License: BSD 3-Clause "New" or "Revised" License

JavaScript 100.00%

webpack-external-import's Introduction

Webpack External Import

import() other chunks and modules from third parties, or other webpack builds themselves! At runtime!

This is now part of Webpack 5 - do not use external-import, read more here: http://module-federation.github.io/

Commitizen friendly Version

Downloads License License

webpack-external-import

This project has been rewritten into the Webpack core. Its far more stable and available on npm [email protected] webpack/webpack#10352

⚠️ This project no longer under development as been enhanced in the webpack 5 core ⚠️

See the examples on module federation in webpack 5

**To jump to the development section click here

Installation

npm

npm install webpack-external-import --save

Yarn

yarn add webpack-external-import

Version 2.0!

Major rewrite which has taken the original concept and built it directly into webpack runtime. A big achievement in this release is tree-shaking support

If you want to read me about what this tool does.

Read the following:

Getting Started

  1. Add webpack-external-import/webpack to your webpack plugins:
// techblog.webpack.config.js
const URLImportPlugin = require("webpack-external-import/webpack");
{
  plugins: [
    new URLImportPlugin({
      manifestName: "website-one"
    })
  ];
}

// otherblog.webpack.config
const URLImportPlugin = require("webpack-external-import/webpack");
{
  plugins: [
    new URLImportPlugin({
      manifestName: "website-two"
    })
  ];
}
  1. If you are interleaving webpack bundles, load their manifests somewhere
// index.js
import { corsImport } from "webpack-external-import";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.jsx";

// using Date.now() for cache busting the file. It should only less than 2kb
corsImport(`http://localhost:3002/importManifest.js?${Date.now()}`).then(() => {
  ReactDOM.render(<App />, document.getElementById("app"));
});

// you could also use native imports

import(
  /* webpackIgnore:true */ `http://localhost:3002/importManifest.js?${Date.now()}`
).then(() => {
  ReactDOM.render(<App />, document.getElementById("app"));
});

Usage

This plugin works with any Webpack driven application

Vanilla JS

This assumes a import manifest was loaded somewhere else in the application already.

If you have not imported manifest then wrap your function in another promise:

corsImport("http://localhost:3002/importManifest.js").then(() => {
  someFunction();
});

As long as the importManifest was loaded - this is how it would be used __webpack_require_.interleaved() expects a module to contain both the module.id and the namespace

This allows external-import to know where to interleave from. __webpack_require_.interleaved([namespace]/[module.id])

Below is an example of interleaving a module from website-2

// import a chunk from another website build with webpack-external-import

__webpack_require__
  .interleaved("website-2/ValidationRules")
  .then(validationRules => {
    // proceed to use as a you would with a normal require statement
    validationRules.validateObject(someObject);
  });

With JSX

ExternalComponent exists for ease of use with React Components and is as SFC using React.Hooks

import { ExternalComponent } from "webpack-external-import";
class SomeComponent extends Component {
  render() {
    return (
      <div>
        <ExternalComponent
          interleave={__webpack_require__.interleaved(
            "website-2/TitleComponent"
          )}
          export="Title"
          title="Some Heading"
        />
      </div>
    );
  }
}

What is the use of webpack-external-import ?

  • Load components over the wire - Pull in components at runtime.
  • Build leaner micro-frontends (MFE) - Micro-frontends can share bundle chunks and resources with each other while remaining self-contained, removing needless code duplication.
  • Split a large, multi-team app into separate deployable chunks while keeping it as one SPA - Large apps can be split into separate feature bundles that can be deployed independently, reducing deployment bottlenecks.
  • Manage common js/vendor files automatically. - Instead of dealing with peer dependencies, externals, or anything else, you can load the dependency from a remote source.
  • LOSA Style frontend architecture - Run multiple apps on a single page.
  • FOSA Style frontend orchestration - Powerful frontend orchestration, self-organizing application architecture. Many builds act as one

Advanced Setup - Injecting Webpack modules from another build

Use the webpack plugin to inject webpack modules from another build into your build.

Important: Make sure manifestName is unique per webpack build. If you have multiple builds, they all need to have a unique manifestName

webpack.config.js

const URLImportPlugin = require("webpack-external-import/webpack");
{
  plugins: [
    new URLImportPlugin({
      manifestName: "website-one"
    })
  ];
}

Mark Files for interleaving

Pretend we have two separate apps that each have their independent build. We want to share a module from one of our apps with the other.

To do this, you must add an interleave object to package.json. The interleave object tells the plugin to make the module accessible through a predictable name.

For example:

// website-two package.json
{
  "name": "some-package-name",
  "interleave": {
    "src/components/Title/index.js": "TitleComponent",
    "src/components/hello-world/index.js": "SomeExternalModule"
  }
}
// website-one App.js
__webpack_require__
  .interleaved("website-3/TitleComponentWithCSSFile")
  .then(TitleComponentWithCSSFile => <TitleComponentWithCSSFile />);

This ensures a easy way for other consumers, teams, engineers to look up what another project or team is willing to allow for interleaving

Working with Webpack Externals

It's important to follow the instructions below if you are planning to use Webpack externals. This plugin must be installed on all builds - it is intended that the build creating providing external is built by this plugin. Externals work best in scenarios where the "host" app should supplying dependencies to an interleaved one.

Providing Externals To support webpack externals, you will need to use provideExternals to specify externals

Note: you must use provideExternals instead of the webpack externals option.

new URLImportPlugin({
  provideExternals: {
    react: "React"
  }
});

Consuming Externals To consume externals, you will need to use useExternals to inform webpack that the interleaved app should use the module specified by provideExternals

new URLImportPlugin({
  useExternals: {
    react: "React"
  }
});

Full Example

WEBSITE-ONE

// app.js

import React, { Component } from "react";
import { ExternalComponent } from "webpack-external-import";
import HelloWorld from "./components/goodbye-world";
import "react-select";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      titleUrl: null,
      manifestLoaded: false,
      loaded: false
    };
  }

  componentDidMount() {
    __webpack_require__
      .interleaved("website-3/TitleComponentWithCSSFile")
      .then(TitleComponentWithCSSFile =>
        console.log(TitleComponentWithCSSFile)
      );
  }

  renderDynamic = () => {
    const { loaded } = this.state;
    if (!loaded) return null;
    return __webpack_require__("SomeExternalModule").default();
  };

  render() {
    return (
      <div>
        <HelloWorld />

        <ExternalComponent
          interleave={__webpack_require__.interleaved(
            "website-2/TitleComponent"
          )}
          export="Title"
          module="TitleComponent"
          title="Some Heading"
        />

        <ExternalComponent
          interleave={__webpack_require__.interleaved(
            "website-3/TitleComponentWithCSSFile"
          )}
          export="Title"
          title="Title Component With CSS File Import"
        />
        {this.renderDynamic()}
      </div>
    );
  }
}

Promise.all([
  corsImport(`http://localhost:3002/importManifest.js?${Date.now()}`),
  corsImport(`http://localhost:3003/importManifest.js?${Date.now()}`)
]).then(() => {
  ReactDOM.render(<App />, document.getElementById("app"));
});

WEBSITE-TWO

// package.json

{
  "name": "website-two",
  "version": "0.0.0-development",
  "author": "Zack Jackson <[email protected]> (https://github.com/ScriptedAlchemy)",
  "interleave": {
    "src/components/Title/index.js": "TitleComponentWithCSSFile",
    "src/components/Title/style.css": "TitleComponentWithCSSFileCSS",
    "src/components/hello-world/index.js": "SomeExternalModule"
  }
}

API:

module.exports = {
  plugins: [
    new URLImportPlugin({
      manifestName: "website-one",
      fileName: "importManifest.js",
      basePath: ``,
      publicPath: `//localhost:3001/`,
      writeToFileEmit: false,
      seed: null,
      filter: null,
      debug: true,
      useExternals: {},
      provideExternals: {}
    })
  ]
};

options.fileName

Type: String
Default: importManifest.js

The manifest filename in your output directory.

options.publicPath

Type: String Default: output.publicPath

A path prefix that will be added to values of the manifest.

options.basePath

Type: String

A path prefix for all keys. Useful for including your output path in the manifest.

options.writeToFileEmit

Type: Boolean
Default: false

If set to true will emit to build folder and memory in combination with webpack-dev-server

options.seed

Type: Object
Default: {}

A cache of key/value pairs to used to seed the manifest. This may include a set of custom key/value pairs to include in your manifest or may be used to combine manifests across compilations in multi-compiler mode. To combine manifests, pass a shared seed object to each compiler's ManifestPlugin instance.

options.filter

Type: Function(FileDescriptor): Boolean

options.testPath

Type: Function(Object, FileDescriptor): Object
Default: src

Test resource path to see if plugin should apply transformations

options.useExternals

Type: Object
Default: {}

Informs the webpack treat the following dependencies as externals. Works the same way externals does.

options.provideExternals

Type: Object
Default: {}

Informs webpack to provide the dependencies listed in the object to other apps using useExternals

ExternalComponent

React Component

Props:

src: string - a url to a javascript file, note it will need to be built by another webpack build running this plugin

interleave: function - the __webpack_require__.interleave() function, which will return a module

export: string - The named export to use as a component from the module being imported

The entry manifest

Each webpack build using the webpack plugin emits a manifest file to the build output directory.

The manifest allows you to find a chunk that you want, even if the name has been hashed.

Below is an example of using the manifest.

In this file, I am importing code from another website/build. My application is loading website two's manifest, which is automatically added to window.entryManifest under the manifestName I set in the webpack plugin. After that, I'm importing a chunk from website-two, in this case - the chunk is code-split.

componentDidMount() {
  corsImport('http://localhost:3002/importManifest.js').then(() => {
      const Title = __webpack_require__
        .interleaved("website-two/TitleComponent")

        Title.then(console.log) // => Module {default: ()=>{}, Title: ()=>{}}
  });
}

DEMO

How to start using the demo In the root directory, run the following

  1. run yarn install
  2. run yarn demo from the root directory
  3. browse to localhost:3001 and you will see components from two other websites

This command will install, all dependencies, build the source for the plugin, install the demo dependencies, run all builds and start serving

Development & Debugging

How to start the demo in debug mode, using node --inspect and connecting to a chrome debugger

This is mainly for debugging the webpack plugin

In the root directory, run the following

  1. yarn install
  2. yarn demo:debug from the root directory
  3. browse to localhost:3001

Note: localhost:3001 is the "consumer app, while the other is the provider app". Both apps work independently and you should check both of them out (they are extremely basic)

Open chrome dev tools and you should see the box highlighted below appear, click on it to connect to the webpack debugger GitHub Logo

Logging

The default compilation removes console log statements via babel-plugin-transform-remove-console, which is run when BABEL_ENV=production (default). To compile a version with logging enabled, run yarn compile directly.

webpack-external-import's People

Contributors

dependabot[bot] avatar ethersage avatar hedgerh avatar kouts avatar maraisr avatar mihaisavezi avatar mizx avatar scriptedalchemy avatar seanvern avatar vkrol avatar y-a-v-a avatar yasserzubair avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webpack-external-import's Issues

Better url joining in utility functions

getChunkPath
getChunkDependencies
importWithDependencies

Need better url joining.
sometimes a url will come out as localhost:3000bundle.js or localhost:3000//bundle.js

Unable to load nested dependencies

Well I was playing around with webpack-external-import plugin and hit a case where not all the dependencies of external build where imported. I would illustrate further with the example.

Assume I have one child app and a parent app. Child acts like a MFE. Parent pulls child app at runtime and should pull all the missing dependencies from child app.
I was looking at your excellent medium article and decided to split every node module in child application by using following optimization.splitChunks in webpack configs:

splitChunks: {
    chunks: 'all',
    maxInitialRequests: Infinity,
    minSize: 0,
    cacheGroups: {
        vendor: {
            test: /[\\/]node_modules[\\/]/,
            name(module) {
                // get the name. E.g. node_modules/packageName/not/this/part.js
                // or node_modules/packageName
                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
                // npm package names are URL-safe, but some servers don't like @ symbols
                return `npm.${packageName.replace('@', '')}`;
            },
        },
    },
 },

Following is the importManifest generated for child app:

if(!window.entryManifest) {window.entryManifest = {}}; window.entryManifest["App1Manifest"] = {
  "App1.js": {
    "path": "App1.js",
    "dependencies": [
      {
        "order": 2,
        "name": "react",
        "id": "LDoPTt+kJa",
        "sourceFiles": [
          "npm.react.js"
        ]
      },
      {
        "order": 1,
        "name": "@babel/runtime/helpers/taggedTemplateLiteral",
        "id": "jkkCvB1L1I",
        "sourceFiles": [
          "npm.babel.js"
        ]
      },
      {
        "order": 3,
        "name": "styled-components",
        "id": "QiEJoypLKc",
        "sourceFiles": [
          "npm.styled-components.js"
        ]
      }
    ],
    "isInitial": true
  },
  "main.js": {
    "path": "main.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.babel.js": {
    "path": "npm.babel.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.emotion.js": {
    "path": "npm.emotion.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.is-what.js": {
    "path": "npm.is-what.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.memoize-one.js": {
    "path": "npm.memoize-one.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.merge-anything.js": {
    "path": "npm.merge-anything.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.object-assign.js": {
    "path": "npm.object-assign.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.process.js": {
    "path": "npm.process.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.prop-types.js": {
    "path": "npm.prop-types.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.react.js": {
    "path": "npm.react.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.react-dom.js": {
    "path": "npm.react-dom.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.react-is.js": {
    "path": "npm.react-is.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.scheduler.js": {
    "path": "npm.scheduler.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.styled-components.js": {
    "path": "npm.styled-components.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.stylis.js": {
    "path": "npm.stylis.js",
    "dependencies": null,
    "isInitial": true
  },
  "npm.stylis-rule-sheet.js": {
    "path": "npm.stylis-rule-sheet.js",
    "dependencies": null,
    "isInitial": true
  },
  "runtime.js": {
    "path": "runtime.js",
    "dependencies": null,
    "isInitial": true
  },
  "./index.html": {
    "path": "./index.html",
    "dependencies": null,
    "isInitial": null
  }
}

So I am using react and styled-components in child app. Same version of react is present in parent app as well. So no need to pull react from child app. But styled-components is not present in parent. So it goes to import missing dependency from child app as expected.

But it appears that dependencies of styled-components(like stylis, stylis-rule-sheet) are not pulled into parent only styled-components was pulled. So parent is not able to resolve stylis and stylis-rule-sheet.

And if we observe importManifest above, styled-components are marked with null as dependencies. I think this is what is causing issue.

Am I missing anything here ?
Are splitting all the npm modules into separate chunks is not expected ?

Unable to run __webpack_require() within a create-react-app

I'm trying to apply this plugin to import/export components between 2 different create-react-apps.
I can load the importManifest and even get the url after resolving importDependenciesOf().
The error happens if I try to import() dynamically and __webpack_require() the component or if I use the ExternalComponent. It actually fails also on __webpack_require() within react.js file https://github.com/ScriptedAlchemy/webpack-external-import/blob/master/src/react.js#L46

I'm not sure if this is related to a missconfiguration of webpack or a webpack plugin that is interfering..

Endless build in a project with large dependencies

Hi!

I've been experimenting an endless build in my project when I tried to install a dependency with a huge dependency tree like aws-amplify.

Please note that without URLImportPlugin configured, my build works. I only get the problem with URLImportPlugin configured.

But if I comments these lines...

while (usedIds.has(hashId.substr(0, len))) {
len++;
}

...everything is working fine!

Do you have any idea about this?

Thanks!

Question: Are nested componenst/modules supported without adding them to interleave

Hi,

Thank you for the work on webpack-external-import.

I have a question, is it possible to have child components of an interleaved component automatically available to the container application.

If we changed the vendor application to import another local component like this:

Screen Shot 2020-02-17 at 5 56 43 pm

Would we have to expose Title3 as interleaved as well, or is there a way to automatically expose Title3?

Issue on running Demo

Hi thx for this package i wan to try the demo like documented.
i get

webpack-external-import git:(master) ✗ npm run demo

[email protected] demo /Users/myuser/dev/webpack-external-import
yarn && yarn && yarn link webpack-external-import && concurrently "yarn compile" "yarn link" "yarn demo:one"  "yarn demo:two"

yarn install v1.21.0
warning package.json: License should be a valid SPDX license expression
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning [email protected]: License should be a valid SPDX license expression
[1/4] 🔍  Resolving packages...
success Already up-to-date.
$ yarn compile && cd manual && yarn
yarn run v1.21.0
warning package.json: License should be a valid SPDX license expression
$ babel src -d .
Successfully compiled 5 files with Babel.
✨  Done in 1.32s.
yarn install v1.21.0
warning package.json: No license field
warning ../package.json: License should be a valid SPDX license expression
warning [email protected]: No license field
[1/4] 🔍  Resolving packages...
success Already up-to-date.
✨  Done in 0.39s.
✨  Done in 3.38s.
yarn install v1.21.0
warning package.json: License should be a valid SPDX license expression
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
warning [email protected]: License should be a valid SPDX license expression
[1/4] 🔍  Resolving packages...
success Already up-to-date.
$ yarn compile && cd manual && yarn
yarn run v1.21.0
warning package.json: License should be a valid SPDX license expression
$ babel src -d .
Successfully compiled 5 files with Babel.
✨  Done in 1.11s.
yarn install v1.21.0
warning package.json: No license field
warning ../package.json: License should be a valid SPDX license expression
warning [email protected]: No license field
[1/4] 🔍  Resolving packages...
success Already up-to-date.
✨  Done in 0.38s.
✨  Done in 2.56s.
yarn link v1.21.0
warning package.json: License should be a valid SPDX license expression
error No registered package found called "webpack-external-import".
info Visit https://yarnpkg.com/en/docs/cli/link for documentation about this command.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] demo: yarn && yarn && yarn link webpack-external-import && concurrently "yarn compile" "yarn link" "yarn demo:one"  "yarn demo:two"
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] demo script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/myuser/.npm/_logs/2019-12-04T08_07_04_663Z-debug.log

the error : error No registered package found called "webpack-external-import".

maybe you know whats wrong ?
thx for your help

system :

OSx 10.15.1
node 10.16.3
npm 6.13.0
yarn 1.21.0

Exclude some modules from being hashed. Using the original name of the request

Right now react is hashed into the id, as seen below. In some cases you might not want to hash a module and instead leave it as react

{
        "order": 1,
        "name": "react",
        "id": "LDoPTt+kJa",
        "sourceFiles": [
          "vendors~main.js"
        ]
}

Implementation

Add another option to this object in src/webpack.js

this.opts = {
      publicPath: null,
      debug: debug || false,
      testPath: 'src',
      basePath: '',
      manifestName: 'unknown-project',
      fileName: 'importManifest.js',
      transformExtensions: /^(gz|map)$/i,
      writeToFileEmit: false,
      seed: null,
      filter: null,
      map: null,
      generate: null,
      hashDigest: 'base64',
      hashDigestLength: 10,
      context: null,
      sort: null,
      hashFunction: 'md4',
      serialize: (manifest) => `if(!window.entryManifest) {window.entryManifest = {}}; window.entryManifest["${opts.manifestName}"] = ${JSON.stringify(
        manifest,
        null,
        2,
      )}`,
      ...opts || {},
    };

Then youll want to look at how i track dependencies, pretty much use something similar to locate the original request, userRequest is "react"

 module.dependencies.forEach((dependency) => {
                if (this.opts.debug && (dependency.request || dependency.userRequest)) {
                  console.groupCollapsed('Dependency', dependency.userRequest, `(${dependency.request})`);
                  console.log(dependency);
                }
                const dependencyModuleSet = dependency.getReference?.()?.module;
                if (!dependencyModuleSet) return null;

Where to apply the new feature:

look for this:

compiler.hooks.compilation.tap('URLImportPlugin', (compilation) => {
        const usedIds = new Set();
        compilation.hooks.beforeModuleIds.tap(
          'URLImportPlugin',
          (modules) => {
            for (const module of modules) {
              if (module.id === null && module.resource) {
                const hash = createHash(this.opts.hashFunction);

                let resourcePath = module.resource;
                if (resourcePath.indexOf('?') > -1) {
                  resourcePath = resourcePath.split('?')[0];
                }

Id console.log(module) and you'll probably be able to find it on the object.

debugging

run yarn demo:debug, then open chrome and you should see the node logo in the inspector, click on it and itll open a console connected to node.js

using with create-react-app generated webpack config..

Seems to be ignoring the magic comment. I assume there is something stripping it before it can be consumed by your plugin.. ? Any thoughts about where to start looking to make adjustments?

I have a fork of create-react-app that i'm modifying to generate a consumable react component / MFE that is loaded into a larger app shell. I am trying to keep the subs in the "create-react-app" ecosystem to lower our ongoing maintenance burden for tooling :)

More info on this plugin and the MFEs

Hey Zack, I've been really interested in some of the things you've working on! Was hoping to find out more about this plugin.

Say my architecture consists of a single SPA that pulls in 15 individual React components that are each published as their own npm packages. (Each component is a whole section of the site.) This results in a bottleneck, where the whole SPA needs to be deployed in order for updated components to be deployed.

I believe this plugin would instead allow me to deploy components individually, and have them pulled in from external URLs at runtime, correct?

I'm curious how we'd manage common dependencies that are used across the SPA/individual components. Currently, the SPA declares them as actual dependencies, while the components declare them as peerDependencies, so the SPA is the source of truth for a common dep and its version. Interested to hear how we'd approach this when the components aren't being bundled in the context of the entire SPA.

Finally, I haven't dug deep in to MFEs. Are there solutions that even come close to doing what this plugin does?

Thanks for humoring my questions. Let me know if you need any docs written!

Semver matching import reuse/caching

If an external module has already been imported and another module requests it, it is already available if the hashed module ID matches. However, different versions of the same library will have different module IDs, while it's possible that a single already loaded version can satisfy the consumer's semver specification.

Is it possible to require an externalized module by a semver description, such as mymodule@^1.0.0, and have it resolve to an already loaded version?

Perhaps the importManifest can contain enough version info for a runtime to evaluate?

make file interface

add to package json something like

{src/somefile.js: someNiceName, src/component/menu.js: Navigation}

Then have babel/webpack automatically expose these file paths. This would allow for an interface of exposed files to be placed in an easy to find location

Sub-resource integrity

Need to investigate and provide a solution for sub-resource integrity.

There’s a few webpack plugins I’ve seen that offer SRI. We will need to offer something to address security concerns

Child getting loaded as empty module in browser when made react as external module in child MFE

Hi Zack,
Thank you very much for creating such awesome plugin to solve one of the common issue with MFE. I was evaluating this plugin and encountered this issue.
I registered react, react-dom and styled-components as external library in MFE webpack configs. While doing so, MFE gets loaded as empty module in parent application at runtime. If I remove external option, it is working as expected. MFE is getting loaded in parent.

Am I doing something incorrect. Is external options yet not supported ?

[v3][question] Does it support composition/transitivity of interleaving components

First of all, thank you for this great library and energy to make this happen.

In v3 will it be possible to create transitive relation or composition of interleaving?

For example if we would have 3 MFEs (web1, web2 and web3) each having a component interleaving imported external component in a way:

  • web1: web1/Component
  • web2: web2/Component(web1/Component)
  • web3: web3/Component(web2/Component)

so effectively component from web3 would have a composition:
web3/Component(web2/Component(web3/Component)).

I tried to do it by adding website3 into examples. And it started working when I added website1 to remotes in website3. It's cool to do it without ever importing website1/Component but I'm interested is there possibility to implicitly conclude website1 remote entry is needed or that website2 can specify it somehow.

I think it's important for scenario where we want to interleave components and create compositions of it, but keep correct versioning of its dependencies. For example website3 could use component directly from website1 but with newer "version" than website2 use.

I guess we could "publish" each version with different name [email protected] or on different url {some-domain}/1.0.0/remoteEntry.js, but it will be difficult to specify all transient remotes.

Sorry if question is not clear or it doesn't make sense and thanks.

Cannot load same component twice

I tried to use this library to see if I can load components from different chunks.It works super when you want to load a component for the first time but there is an issue when you want to interleave the same component twice. Basically it does not do the request to the app at all..
I tried to load manifest everytime I load the component but still it doesn't work. It does not go to the console.log after the interleaved and also there is not an error at all..I tried to fetch the components once and store them but then I have an issue with the internal deps.Can you give me an advice what's wrong?

P.S. I am using webpack-import 2.2.3 version.


const BasicComponent = (props) => {
    const [components, setComponents] = useState(null);

    useEffect(() => {
        async function getComp() {
            try {
                await corsImport(`http://test.local:3002/importManifest.js?${Date.now()}`);
                // eslint-disable-next-line no-undef
                __webpack_require__
                    .interleaved(`${props.components[0].app}/${props.components[0].name}`)
                    .then((comp) => {
                        // const fetchedComponents = await Promise.all(props.components.map((x) => __webpack_require__.interleaved(`${x.app}/${x.name}`)));
                        console.log('Testing');
                        setComponents([comp.default]);
                    });

            } catch (err) {
                console.log('error', err);
            }

        }
        getComp();
    }, []);

    return (
        <div>
            {!components &&
                <p>Loading..</p>
            }
            {components && components.map((x, index) => {
                return React.createElement(x);
            })}
        </div>
    );
};

interleave vs magic comments

Out of curiosity, what is the difference using the magic comment (/externalize/) inside the actual component vs using the interleave in package.json?

The reason for my question is that the magic comment is not part of the documentation any longer. But it still seems to work.

Great package btw!

Simple Example for Angular 9

How can we use this plugin in Angular 9. This is like most needed feature in Angular.
If someone can share a simple example with Angular would be really great. Not sure when Angular will support this feature out of the box even if Webpack 5 gets released.

ChunkLoadError when service prod resources

After stumbling across this plugin via your MicroFrontEnd Architecture article i've been playing around with it for my build and had it working very successfully while both apps were running inside webpack-dev-server.

Its a very simple setup, App (A) dynamically loads a single react component from App (B).

I then did a full prod build (B) and served the dist folder locally with the npm serve package.
After switching the import url in (A) to point at (B)'s dist server, the import started throwing a ChunkLoadError at bootstrap:259 (inside the onScriptComplete function).

I can see in the network logs that both the importManifest.js and the chunk file itself have been loaded into the browser successfully, so I'm totally lost as to why there's a behaviour change.

Any suggestions on what to poke next?

What is the best licence choice?

In #104, @yordis asked

is there any strong reason why MIT couldn't be the license for the package?

This will avoid hustle for some folks due to legal reasons from some organizations 😢

To which @ScriptedAlchemy replied:

Whats a better restrictive license. I want people to be able to use it but want to restrict as much as possible.

Mark files to be code split

Right now, I can use externalize magic comment to provide a human-readable module name. However, marking a file to for a human-readable module name does not split that marked file out into a chunk.

Current Scenario

Website A

In order to ensure that website A provides me with an independent chunk, I do the following import(/* webpackChunkName: "hello-world"*/ './components/hello-world')

This leverages standard Webpack functionality and ensures that I end up with a hello-world.js file, which can be consumed independently inside Website B.

The goal

I don't want to be forced to code split a file using import(), if a file is marked to be externalized, then webpack should automatically chunk that file out on its own.

After some research, I have found a plugin which allows for modifying how the assets are chunked out and grouped together. A good starting place would be to look at how this is done in this plugin and extract the main mechanism used.

Could this be used as a plugin framework?

I've got an electron app that I'm interested in adding plugin support for..

My though was it would be easiest to just allow people to implement a component with specific prop types and then have the ability to "register" components with predefined contribution points via external paths:

{
  "welcome-banner": "path/to/my/welcome/banner"
} 

I understand this is kinda a security nightmare, but this is an internal tool and we can trust the code we are adding.

Curious if this plugin might help accomplish this goal

Options not working

options.publicPath
options.basePath
options.fileName

Don't seem to work, I'm unable to set the destination of the manifest as expected

SyncWaterfallHook is not a constructor

Attempting to use this plugin. When I add the plugin to the webpack plugins, I get the following error when running webpack-dev-server:

TypeError: SyncWaterfallHook is not a constructor at URLImportPlugin.apply ...\node_modules\webpack-external-import\webpack\index.js:373:58

Any suggestions as to where to look?

Could you update babel-traverse dependency? As core-js@2 has been deprecated.

Hi there,
Our project depends on extract-css-chunks-webpack-plugin and npm warn us with the following warning:

npm WARN deprecated [email protected]: core-js@<3.0 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.

The dependency graph is as the following

extract-css-chunks-webpack-plugin > webpack-external-import > babel-traverse > babel-runtime > core-js

The new version of babel-traverse does not depends on core-js anymore. So, I guess this is the right place to start updating.

I don't really have a practical issue apart from the warning. Just giving you guys a nudge.

Does not work with yalc

I am unable to develop a package locally with yalc with module federation. This is particularly a need for developing stateful packages.

Steps to reproduce:

  1. Create a local package called my-package. Run yalc publish inside it.
  2. In an app using module federation, run yalc add my-package and add my-package to shared in ModuleFederationPlugin.
  3. Run webpack.

Result: An error is thrown:

ERROR in overridable my-package
Module not found: Error: Can't resolve 'my-package' in '/Users/nick/my-app'

Bug/Question: ExternalComponent is rendering component only for once

I am using this plugins's v2. It is working quite smoothly except some issues. One issue I found that I am using ExternalComponent to render my interleaved component as mentioned into the README. I have a side navigation that switch between two different MFs and I also have a config.json that have info about each MFs config (proxy etc.) and key is the name of MF. I add all my MFs to an Apps constant like following:

const Apps = {};
Object.keys(config).forEach(appName => {
  Apps[appName] = props => <MicroFrontend appName={appName} location={props.location} />
})

Below is code for MicroFrontend component:

import React from 'react';
import { ExternalComponent } from 'webpack-external-import';
function MicroFrontend({appName, location}) {

return (
   <div className='mf-wrapper'>
       <ExternalComponent
          interleave={__webpack_require__.interleaved(
            `website-${appName}/${appName}-app`
          )}
          export={`${appName}App`}
        />
  </div>
)
}

I am loading all importManifest files in my index.js and they are loaded without any errors.

My side navigation renders Apps[appName] based on the route (I am using react-router-dom for it). Initially my MFs load fine but when I again go to previously visited Router my MFs don't load. I found that everything loads fine except component renederd by ExternalComponent. I tried to fetch the component in an effect but it doesn't work. Is this a problem with ExtermalComponent?

PS: I looked a bit into __webpack_require__.interleaved function exported from this module. It has some checks that if chunk is loaded then it doesn't load it again (not sure but my understanding). If fetching the chunk again is not a good idea then how I will rerender my component that is based on previously loaded chunk.

Async/Await?

Need to check/make plugin work with async await syntax

importManifest => external module + dependencies waterfall

When you want to import an external module you need to load the manifest first, then you can load any of the modules. There's a waterfall that can add more latency than dynamically importing most code-split bundles:

import('http://website1.com/js/theExampleFile.js') // first request
.then(({ExampleModule})=>{ }); // second request

// and what if that module has more dynamic imports?

You can preload them, but would be nice to have a utility to do that and handle storing/accessing the paths later so you don't have to pass the manifest around all over your app. Or maybe if the manifest isn't too big it can be bundled into the consumers app so it already knows about the dependencies.

When you consider that there may be a chain of other dependencies the waterfall can get longer. It would be helpful if the manifest could tell you all of the sub-dependencies you're going to need.

Uncaught ReferenceError: installedChunks is not defined

Hi 😊

I've been trying webpack-external-import on a project with MinChunkSizePlugin and it would seem that using MinChunkSizePlugin and URLImportPlugin together may cause issues.

Indeed, if Webpack create only one chunk (because of a large minChunkSize value), I get this error:

Uncaught ReferenceError: installedChunks is not defined

I don't really know how Webpack works but the webpackJsonpCallback function is missing in the non-working version. So I think that Webpack can't load properly the external chunk 🤔

Hope it helps 😉

TypeError: Cannot read property 'call' of undefined at webpack_require (bootstrap:790)

Hi, friend.
I use webpack-external-import to import another project's react component.I am often prompted for error like this.

image
bootstrap:790 Uncaught (in promise) TypeError: Cannot read property 'call' of undefined
at webpack_require (bootstrap:790)
at fn (bootstrap:150)
at Object. (index.js:5)
at Object.okex/account-history (account-history.js:132)
at webpack_require (bootstrap:790)
at bootstrap:1106

It seems the dependencies in the project that i cross imported, it is loaded later than the project loaded. Is there any way to solve the problem of loading order?
Thanks.

__webpack_require__ is not defined

I am attempting to use the ExternalComponent per the docs example within a React app. Once I compile it to run locally, I'm getting the following error:

__webpack_require__ is not defined

I'm definitely missing something, aren't I?

I have loaded the manifest and can see that it's loading correctly, it's just throwing this error. I'm on Webpack 4.41.2.

npm tag latest points to upcoming v3-beta

Hi, just noticed that the latest tag on npm currently points to the latest v3-beta.

So when I install it using npm i --save webpack-external-import this currently gives me ^3.0.0-beta.3 with the readme for the v2.

I think it would make more sense to point the latest tag to the v2 until the v3 is out of beta?

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: Preset name not found within published preset config (:prConcurrentLimit2)

Do not transform other thennable promises.

Right now, in the babel plugin

Identifier(p) { method ends up trying to transform normal promises, we need to walk up the sibling and check if its part of an import statement or not

Two identical ExteranlComponents will only render one

As the subject says, if you have two identical ExternalComponent on the same page, then, strangely, it will only render one of them. It seems like the interleave promise in v2Effect never resolves on the second component.

To reproduce, copy/duplicate the website-2/TitleComponent into Website1s App.jsx.

GPL License

Hello,
I wanted to ask you what's the reason for the GPL license.

We have a problem right now because we bundle nuxt in our commercial app and only this dependency has GPL. All parent dependencies of webpack-external-import have MIT. As sublicensing is not allowed, we would need to change our license of our whole app to GPL only because of this single dependency.

[email protected]
└─┬ @nuxt/[email protected]
└─┬ [email protected]
└── [email protected]

Is there are reason for this or would it be possible to change the license, e.g. to MIT or something?

Broken Integration test

@ethersage I'm happy to say that this project is starting to move forward again. It's gone through a major cleanup.

I've been able to fix all the tests but one.

I was wondering if you'd have any capacity to provide some guidance on what i need to do :)

 ● external script › should console.log

    TypeError: react__WEBPACK_IMPORTED_MODULE_1___default.a.createElement is not a function

Id muuuch appreciate it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.