├── .gitignore ├── .travis.yml ├── README.md ├── __test__ ├── build.js ├── fixtures │ ├── app.js │ ├── foo.js │ ├── project1 │ │ ├── index.js │ │ └── webpack.config.js │ └── project2 │ │ ├── index.js │ │ └── webpack.config.js └── index.spec.js ├── babel.config.js ├── examples ├── website1 │ ├── package.json │ ├── src │ │ ├── App.jsx │ │ ├── Footer.jsx │ │ ├── HelloWorld.jsx │ │ ├── bootstrap.jsx │ │ ├── index.js │ │ └── template.html │ ├── webpack.config.js │ └── yarn.lock └── website2 │ ├── package.json │ ├── src │ ├── App.jsx │ ├── Footer.jsx │ ├── HelloWorld.jsx │ ├── Title.jsx │ ├── bootstrap.jsx │ ├── index.js │ └── template.html │ ├── webpack.config.js │ └── yarn.lock ├── license ├── package.json ├── src ├── ContainerEntryDependency.js ├── ContainerEntryModule.js ├── ContainerEntryModuleFactory.js ├── ContainerExposedDependency.js ├── ModuleFederationPlugin.js ├── RemoteModule.js ├── SharedModule.js ├── SharedModuleFactoryPlugin.js ├── index.js ├── package.json └── webpack │ └── lib │ ├── ModuleFactory.js │ ├── RuntimeGlobals.js │ └── propertyAccess.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | .tmp 4 | dist 5 | !src/webpack/lib 6 | .idea 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | before_script: 5 | - yarn install 6 | - yarn build 7 | script: yarn test 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/alibaba/module-federation4.svg?branch=master)](https://travis-ci.org/alibaba/module-federation4) 2 | 3 | ## module-federation4 4 | 5 | `webpack-plugin-module-federation` for Webpack4, backport from https://github.com/ScriptedAlchemy/webpack-external-import 6 | 7 | ## State 8 | 9 | not production ready at present. 10 | 11 | ## Usage 12 | 13 | ```shell 14 | npm install --save-dev webpack-plugin-module-federation 15 | ``` 16 | 17 | Configure your `webpack.config.js` 18 | 19 | ```js 20 | const ModuleFederationPlugin = require('webpack-plugin-module-federation'); 21 | 22 | module.exports = { 23 | output: { 24 | publicPath: 'http://localhost:3002/', 25 | }, 26 | plugins: [ 27 | new ModuleFederationPlugin({ 28 | name: '_federation_website2', 29 | library: 'website2', 30 | filename: 'remoteEntry.js', 31 | libraryTarget: 'global', 32 | remotes: { 33 | 'website1': 'website1' 34 | }, 35 | expose: { 36 | Title: './src/Title', 37 | App: './src/App' 38 | }, 39 | }), 40 | ] 41 | }; 42 | ``` 43 | 44 | ## Import module from remote 45 | 46 | In remote project, configure `webpack.config.js`. 47 | 48 | ```js 49 | const ModuleFederationPlugin = require('webpack-plugin-module-federation'); 50 | 51 | module.exports = { 52 | output: { 53 | publicPath: 'http://localhost:3001/', 54 | }, 55 | plugins: [ 56 | new ModuleFederationPlugin({ 57 | name: '_federation_website1', 58 | library: 'website1', 59 | filename: 'remoteEntry.js', 60 | libraryTarget: 'global', 61 | remotes: { 62 | 'website2': '_federation_website2' 63 | }, 64 | expose: { 65 | App: './src/App' 66 | }, 67 | }), 68 | ] 69 | }; 70 | ``` 71 | 72 | Add `remoteEntry` in your HTML 73 | 74 | ```html 75 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | 83 | ``` 84 | 85 | Then use dynamic import 86 | 87 | ```jsx 88 | import React, { lazy, Suspense, useState } from 'react'; 89 | import Footer from './Footer'; 90 | 91 | const Title = lazy(() => import('website2/Title')); // federated 92 | 93 | export default () => { 94 | return ( 95 | <> 96 | 97 | 98 | </Suspense> 99 | <p> 100 | This app loads the heading above from website2, and doesnt expose 101 | anything itself. 102 | </p> 103 | <Footer /> 104 | </> 105 | ); 106 | }; 107 | ``` 108 | 109 | ## Exmaple 110 | 111 | See [examples here](./examples). 112 | 113 | ```shell 114 | git clone 115 | yarn install 116 | yarn build 117 | yarn install:example 118 | yarn dev:example 119 | ``` 120 | 121 | Open http://localhost:3001 122 | 123 | ## Preview 124 | 125 | ![preview](https://img.alicdn.com/tfs/TB1kD5fDeT2gK0jSZFvXXXnFXXa-600-311.gif) 126 | -------------------------------------------------------------------------------- /__test__/build.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const rimraf = require('rimraf'); 3 | const shell = require('shelljs'); 4 | 5 | const tmp = join(__dirname, '.tmp'); 6 | const src = join(__dirname, 'fixtures'); 7 | 8 | shell.rm('-r', tmp); 9 | shell.cp('-r', src, tmp); 10 | 11 | shell.exec(`cd ${join(tmp, './project1')} && npx webpack`); 12 | shell.exec(`cd ${join(tmp, './project2')} && npx webpack`); 13 | -------------------------------------------------------------------------------- /__test__/fixtures/app.js: -------------------------------------------------------------------------------- 1 | import mri from 'mri'; 2 | import foo from './foo'; 3 | 4 | mri([]); 5 | 6 | console.log(` 7 | ENV: process.env.NODE_ENV 8 | BAR: DEMO_BAR 9 | FOO: FOO_BAR 10 | BAZ: FOO_BAZ 11 | ${ foo() } 12 | `); 13 | -------------------------------------------------------------------------------- /__test__/fixtures/foo.js: -------------------------------------------------------------------------------- 1 | var FOO = DEMO_FOO; 2 | 3 | module.exports = () => `foo: ${ FOO }`; 4 | -------------------------------------------------------------------------------- /__test__/fixtures/project1/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'hello project1'; 2 | -------------------------------------------------------------------------------- /__test__/fixtures/project1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const ModuleFederationPlugin = require('../../../lib'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: { 6 | main: path.join(__dirname, './index.js'), 7 | }, 8 | output: { 9 | publicPath: path.join(__dirname, './dist'), 10 | }, 11 | target: 'node', 12 | resolve: { 13 | extensions: ['.jsx', '.js', '.json'], 14 | }, 15 | optimization: { 16 | minimize: false, 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.jsx?$/, 22 | loader: require.resolve('babel-loader'), 23 | options: { 24 | rootMode: 'upward', 25 | presets: [], 26 | }, 27 | }, 28 | ], 29 | }, 30 | plugins: [ 31 | new ModuleFederationPlugin({ 32 | name: '_federation_project1', 33 | library: { 34 | type: 'global', 35 | name: '_federation_project1', 36 | }, 37 | filename: 'remoteEntry.js', 38 | exposes: { 39 | index: './index.js' 40 | }, 41 | }), 42 | ], 43 | }; 44 | -------------------------------------------------------------------------------- /__test__/fixtures/project2/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hello: 'hello project2', 3 | remoteHello: import('project1/index').then(mod => mod.default), 4 | }; 5 | 6 | -------------------------------------------------------------------------------- /__test__/fixtures/project2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const ModuleFederationPlugin = require('../../../lib'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: { 6 | main: path.join(__dirname, './index.js'), 7 | }, 8 | output: { 9 | publicPath: path.join(__dirname, './dist'), 10 | libraryTarget: 'commonjs', 11 | }, 12 | target: 'node', 13 | resolve: { 14 | extensions: ['.jsx', '.js', '.json'], 15 | }, 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.jsx?$/, 20 | loader: require.resolve('babel-loader'), 21 | options: { 22 | rootMode: 'upward', 23 | presets: [], 24 | }, 25 | }, 26 | ], 27 | }, 28 | optimization: { 29 | minimize: false, 30 | }, 31 | plugins: [ 32 | new ModuleFederationPlugin({ 33 | name: '_federation_project2', 34 | library: { 35 | type: 'global', 36 | name: '_federation_project2', 37 | }, 38 | filename: 'remoteEntry.js', 39 | remotes: { 40 | 'project1': '_federation_project1' 41 | }, 42 | }), 43 | ], 44 | }; 45 | -------------------------------------------------------------------------------- /__test__/index.spec.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const tmpdir = path.join(__dirname, '.tmp'); 5 | 6 | const project1dir = path.join(tmpdir, './project1/dist/'); 7 | const project2dir = path.join(tmpdir, './project2/dist/'); 8 | 9 | require('./build'); 10 | 11 | describe('Build', () => { 12 | test('should bundle', () => { 13 | expect(fs.existsSync(path.join(project1dir, './main.js'))).toBeTruthy(); 14 | expect(fs.existsSync(path.join(project2dir, './main.js'))).toBeTruthy(); 15 | }); 16 | 17 | test('should have remoteEntry', () => { 18 | expect(fs.existsSync(path.join(project1dir, './remoteEntry.js'))).toBeTruthy(); 19 | expect(fs.existsSync(path.join(project2dir, './remoteEntry.js'))).toBeTruthy(); 20 | }); 21 | 22 | }); 23 | 24 | describe('Remote module', () => { 25 | test('can import remote module', async () => { 26 | // load remoteEntry of project1 27 | require(path.join(project1dir, './remoteEntry.js')); 28 | 29 | const { 30 | hello, 31 | remoteHello, 32 | } = require(path.join(project2dir, './main.js')); 33 | 34 | expect(hello).toEqual('hello project2'); 35 | 36 | expect(await remoteHello).toEqual('hello project1'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | require.resolve('@babel/preset-env'), 5 | { 6 | targets: { 7 | node: true, 8 | }, 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | require.resolve('@babel/plugin-proposal-optional-chaining'), 14 | require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), 15 | ], 16 | }; -------------------------------------------------------------------------------- /examples/website1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website1", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "concurrently --raw -k \"webpack --watch\" \"serve dist/ -p 3001\"", 7 | "serve": "concurrently --raw -k \"serve dist/ -p 3001\"", 8 | "build": "webpack --mode production" 9 | }, 10 | "dependencies": { 11 | "react": "^16.12.0", 12 | "react-dom": "^16.12.0", 13 | "react-router-dom": "^5.1.2" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.9.0", 17 | "@babel/preset-react": "^7.8.3", 18 | "babel-loader": "^8.0.6", 19 | "concurrently": "^5.1.0", 20 | "html-webpack-plugin": "^4.2.0", 21 | "serve": "^11.3.0", 22 | "webpack": "^4.42.1", 23 | "webpack-cli": "^3.3.11" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/website1/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from 'react'; 2 | import HelloWorld from './HelloWorld'; 3 | 4 | import { 5 | BrowserRouter as Router, 6 | Switch, 7 | Route, 8 | Link 9 | } from "react-router-dom"; 10 | 11 | 12 | const Website2 = lazy(() => import('website2/App').then(mod => mod.default)); 13 | 14 | export default () => <Router> 15 | <nav> 16 | <ul> 17 | <li> 18 | <Link to="/">Home</Link> 19 | </li> 20 | <li> 21 | <Link to="/website2">Website2</Link> 22 | </li> 23 | </ul> 24 | </nav> 25 | <Switch> 26 | <Route path="/website2"> 27 | <Suspense fallback={<div>loading</div>}> 28 | <Website2 /> 29 | </Suspense> 30 | </Route> 31 | <Route path="/"> 32 | <HelloWorld /> 33 | </Route> 34 | </Switch> 35 | </Router>; 36 | -------------------------------------------------------------------------------- /examples/website1/src/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => <h1>Bye bye website1!</h1>; 4 | -------------------------------------------------------------------------------- /examples/website1/src/HelloWorld.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense, useState } from 'react'; 2 | import Footer from './Footer'; 3 | 4 | // import from another online project 5 | const Title = lazy(() => import('website2/Title').then(mod => mod.default)); 6 | 7 | export default () => { 8 | return ( 9 | <> 10 | <Suspense fallback={<div>loading</div>}> 11 | <Title /> 12 | </Suspense> 13 | <p> 14 | This app loads the heading above from website2, and doesnt expose 15 | anything itself. 16 | </p> 17 | <Footer /> 18 | </> 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /examples/website1/src/bootstrap.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App'; 4 | 5 | render(<App/>, document.getElementById('app')); 6 | -------------------------------------------------------------------------------- /examples/website1/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap.jsx'); 2 | -------------------------------------------------------------------------------- /examples/website1/src/template.html: -------------------------------------------------------------------------------- 1 | <html> 2 | <head> 3 | </head> 4 | <body> 5 | <div id="app"></div> 6 | <script src="https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.development.js"></script> 7 | <script src="https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.development.js"></script> 8 | <script src="http://localhost:3002/remoteEntry.js"></script> 9 | </body> 10 | </html> 11 | -------------------------------------------------------------------------------- /examples/website1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const ModuleFederationPlugin = require('../../'); 3 | 4 | module.exports = { 5 | entry: './src/index', 6 | cache: false, 7 | 8 | mode: 'development', 9 | devtool: 'source-map', 10 | 11 | optimization: { 12 | minimize: false, 13 | }, 14 | 15 | output: { 16 | publicPath: 'http://localhost:3001/', 17 | }, 18 | 19 | resolve: { 20 | extensions: ['.jsx', '.js', '.json'], 21 | }, 22 | 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.jsx?$/, 27 | loader: require.resolve('babel-loader'), 28 | options: { 29 | rootMode: 'upward', 30 | presets: [require.resolve('@babel/preset-react')], 31 | }, 32 | }, 33 | ], 34 | }, 35 | 36 | plugins: [ 37 | new ModuleFederationPlugin({ 38 | remotes: { 39 | 'website2': '_federation_website2' 40 | }, 41 | // shared is not support now 42 | // shared: ['react', 'react-dom'] 43 | }), 44 | new HtmlWebpackPlugin({ 45 | template: './src/template.html', 46 | chunks: ['main'], 47 | }), 48 | ], 49 | 50 | externals: { 51 | 'react': 'React', 52 | 'react-dom': 'ReactDOM' 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /examples/website2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website2", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "concurrently --raw -k \"webpack --watch\" \"serve dist/ -p 3002\"", 7 | "serve": "concurrently --raw -k \"serve dist/ -p 3002\"", 8 | "build": "webpack" 9 | }, 10 | "dependencies": { 11 | "react": "^16.12.0", 12 | "react-dom": "^16.12.0" 13 | }, 14 | "devDependencies": { 15 | "@babel/preset-react": "^7.8.3", 16 | "babel-loader": "^8.0.6", 17 | "concurrently": "^5.1.0", 18 | "serve": "^11.3.0", 19 | "html-webpack-plugin": "^4.2.0", 20 | "webpack": "^4.42.1", 21 | "webpack-cli": "^3.3.11" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/website2/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from 'react'; 2 | import HelloWorld from './HelloWorld'; 3 | 4 | export default () => <HelloWorld />; 5 | -------------------------------------------------------------------------------- /examples/website2/src/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => <h1>Bye bye website2!</h1>; 4 | -------------------------------------------------------------------------------- /examples/website2/src/HelloWorld.jsx: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from 'react'; 2 | // import Footer from 'website1/Footer'; 3 | import Footer2 from './Footer'; 4 | 5 | const Title = lazy(() => import('./Title')); 6 | 7 | export default () => ( 8 | <> 9 | <Suspense fallback={'fallback'}> 10 | <Title /> 11 | </Suspense> 12 | <p>This is Website 2</p> 13 | <Footer2 /> 14 | </> 15 | ); 16 | -------------------------------------------------------------------------------- /examples/website2/src/Title.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => <h1>Hello world!</h1>; 4 | -------------------------------------------------------------------------------- /examples/website2/src/bootstrap.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App'; 4 | 5 | render(<App />, document.getElementById('app')); 6 | -------------------------------------------------------------------------------- /examples/website2/src/index.js: -------------------------------------------------------------------------------- 1 | import('./bootstrap.jsx'); 2 | -------------------------------------------------------------------------------- /examples/website2/src/template.html: -------------------------------------------------------------------------------- 1 | <html> 2 | <head> 3 | 4 | </head> 5 | <body> 6 | <div id="app"></div> 7 | 8 | <script src="https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.development.js"></script> 9 | <script src="https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.development.js"></script> 10 | <script src="http://localhost:3001/remoteEntry.js"></script> 11 | </body> 12 | </html> 13 | -------------------------------------------------------------------------------- /examples/website2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const ModuleFederationPlugin = require('../../'); 3 | 4 | module.exports = { 5 | entry: { 6 | main: './src/index', 7 | }, 8 | cache: false, 9 | devtool: 'source-map', 10 | mode: 'development', 11 | 12 | optimization: { 13 | minimize: false, 14 | }, 15 | 16 | output: { 17 | publicPath: 'http://localhost:3002/', 18 | }, 19 | 20 | resolve: { 21 | extensions: ['.jsx', '.js', '.json'], 22 | }, 23 | 24 | externals: { 25 | 'react': 'React', 26 | 'react-dom': 'ReactDOM' 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.jsx?$/, 33 | loader: require.resolve('babel-loader'), 34 | options: { 35 | rootMode: 'upward', 36 | presets: [require.resolve('@babel/preset-react')], 37 | }, 38 | }, 39 | ], 40 | }, 41 | 42 | plugins: [ 43 | new ModuleFederationPlugin({ 44 | name: '_federation_website2', 45 | library: { 46 | type: 'var', 47 | name: '_federation_website2', 48 | }, 49 | filename: 'remoteEntry.js', 50 | // shared is not support now 51 | // shared: ['react', 'react-dom'], 52 | remotes: { 53 | 'website1': 'website1' 54 | }, 55 | exposes: { 56 | Title: './src/Title', 57 | App: './src/App' 58 | }, 59 | }), 60 | new HtmlWebpackPlugin({ 61 | template: './src/template.html', 62 | chunks: ['main'], 63 | }), 64 | ], 65 | 66 | }; 67 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | --------------- 3 | 4 | Copyright © 2020 Alibaba All rights reserved. 5 | Copyright © 2018 Zackary Jackson All rights reserved. 6 | Copyright © 2018 ScriptedAlchemy LLC All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | * Neither the name of “ScriptedAlchemy” nor the names of its contributors may be used to 16 | endorse or promote products derived from this software without specific prior 17 | written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-plugin-module-federation", 3 | "version": "1.0.0-beta-1", 4 | "description": "", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "yarn build && jest", 8 | "dev": "tsc -w", 9 | "build": "tsc", 10 | "dev:example": "cross-env NODE_ENV=development concurrently --raw \"cd examples/website1 && npm start\" \"cd examples/website2 && npm start\"", 11 | "install:example": "concurrently --raw \"cd examples/website1 && yarn install\" \"cd examples/website2 && yarn install\"", 12 | "clean:example": "concurrently --raw \"cd examples/website1 && rm -rf node_modules\" \"cd examples/website2 && rm -rf node_modules\"", 13 | "clean": "npm run clean:example && rm -rf node_modules" 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@babel/core": "^7.9.0", 19 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", 20 | "@babel/plugin-proposal-optional-chaining": "^7.9.0", 21 | "@babel/preset-env": "^7.9.5", 22 | "@types/expect": "^24.3.0", 23 | "@types/jest": "^25.2.1", 24 | "@types/webpack": "^4.41.12", 25 | "babel-loader": "^8.1.0", 26 | "concurrently": "^5.1.0", 27 | "cross-env": "^7.0.2", 28 | "jest": "^25.4.0", 29 | "serve": "^11.3.0", 30 | "shelljs": "^0.8.3", 31 | "typescript": "^3.8.3", 32 | "webpack": "^4.42.1", 33 | "webpack-cli": "^3.3.11" 34 | }, 35 | "files": [ 36 | "index.js", 37 | "lib" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/ContainerEntryDependency.js: -------------------------------------------------------------------------------- 1 | import Dependency from 'webpack/lib/Dependency'; 2 | 3 | export default class ContainerEntryDependency extends Dependency { 4 | constructor(dependencies, name) { 5 | super(); 6 | this.exposedDependencies = dependencies; 7 | this.optional = true; 8 | this.loc = { name }; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ContainerEntryModule.js: -------------------------------------------------------------------------------- 1 | import Module from 'webpack/lib/Module'; 2 | import AsyncDependenciesBlock from 'webpack/lib/AsyncDependenciesBlock'; 3 | import RuntimeGlobals from './webpack/lib/RuntimeGlobals'; 4 | import Template from 'webpack/lib/Template'; 5 | import { ConcatSource, OriginalSource, RawSource } from 'webpack-sources'; 6 | 7 | const SOURCE_TYPES = new Set(['javascript']); 8 | const RUNTIME_REQUIREMENTS = new Set([ 9 | RuntimeGlobals.definePropertyGetters, 10 | RuntimeGlobals.exports, 11 | RuntimeGlobals.returnExportsFromRuntime, 12 | ]); 13 | 14 | export default class ContainerEntryModule extends Module { 15 | constructor(dependency) { 16 | super('javascript/dynamic', null); 17 | this.expose = dependency?.exposedDependencies; 18 | } 19 | 20 | getSourceTypes() { 21 | return SOURCE_TYPES; 22 | } 23 | 24 | basicFunction(args, body) { 25 | return `function(${args}) {\n${Template.indent(body)}\n}`; 26 | } 27 | 28 | identifier() { 29 | return `container entry ${JSON.stringify( 30 | this.expose?.map(item => item.exposedName), 31 | )}`; 32 | } 33 | 34 | readableIdentifier() { 35 | return `container entry`; 36 | } 37 | 38 | needBuild(context, callback) { 39 | return callback(null, !this.buildMeta); 40 | } 41 | 42 | /** 43 | * Removes all dependencies and blocks 44 | * @returns {void} 45 | */ 46 | clearDependenciesAndBlocks() { 47 | this.dependencies.length = 0; 48 | this.blocks.length = 0; 49 | } 50 | 51 | build(options, compilation, resolver, fs, callback) { 52 | this.buildMeta = {}; 53 | this.buildInfo = { 54 | strict: true, 55 | }; 56 | 57 | this.clearDependenciesAndBlocks(); 58 | 59 | for (const dep of (this.expose || [])) { 60 | const block = new AsyncDependenciesBlock( 61 | undefined, 62 | dep.loc, 63 | dep.userRequest, 64 | ); 65 | block.addDependency(dep); 66 | this.addBlock(block); 67 | } 68 | 69 | callback(); 70 | } 71 | 72 | source(depTemplates, runtimeTemplate) { 73 | const runtimeRequirements = RUNTIME_REQUIREMENTS; 74 | const getters = []; 75 | 76 | let result = ''; 77 | 78 | for (const block of this.blocks) { 79 | const { 80 | dependencies: [dep], 81 | } = block; 82 | const name = dep.exposedName; 83 | const mod = dep.module; 84 | const request = dep.userRequest; 85 | 86 | let str; 87 | 88 | if (!mod) { 89 | str = runtimeTemplate.throwMissingModuleErrorBlock({ 90 | request: dep.userRequest, 91 | }); 92 | } else { 93 | str = `return ${runtimeTemplate.blockPromise({ 94 | block, 95 | message: request, 96 | })}.then(${this.basicFunction( 97 | '', 98 | `return ${runtimeTemplate.moduleRaw({ 99 | module: mod, 100 | request, 101 | weak: false, 102 | runtimeRequirements, 103 | })}`, 104 | )});`; 105 | } 106 | 107 | getters.push( 108 | `${Template.toNormalComment( 109 | `[${name}] => ${request}`, 110 | )}"${name}": ${this.basicFunction('', str)}`, 111 | ); 112 | } 113 | 114 | result = [ 115 | `\n${"var"} __MODULE_MAP__ = {${getters.join(',')}};`, 116 | `\n${"var"} __GET_MODULE__ = ${this.basicFunction( 117 | ['module'], 118 | `return typeof __MODULE_MAP__[module] ==='function' ? __MODULE_MAP__[module].apply(null) : Promise.reject(new Error('Module ' + module + ' does not exist.'))`, 119 | )};`, 120 | `\n\n module.exports = {\n`, 121 | Template.indent([ 122 | `get: ${this.basicFunction( 123 | 'id', 124 | 'return __GET_MODULE__(id)', 125 | )},`, 126 | 127 | `override: ${this.basicFunction( 128 | 'obj', 129 | `Object.assign(__MODULE_MAP__, obj)`, 130 | )},`, 131 | // ${RuntimeGlobals.definePropertyGetters( 132 | // ['module', 'getter'], 133 | // '__webpack_require__.shared[module] = getter;', 134 | // )}` 135 | ]), 136 | `};`, 137 | // `)`, 138 | ].join(''); 139 | 140 | if (this.useSourceMap) { 141 | return new OriginalSource(result, this.identifier()); 142 | } else { 143 | return new RawSource(result); 144 | } 145 | } 146 | 147 | /** 148 | * Get a list of runtime requirements 149 | * @param {SourceContext} context context for code generation 150 | * @returns {Iterable<string> | null} required runtime modules 151 | */ 152 | getRuntimeRequirements(context) { 153 | return [RuntimeGlobals.module, RuntimeGlobals.require]; 154 | } 155 | 156 | 157 | size(type) { 158 | return 42; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/ContainerEntryModuleFactory.js: -------------------------------------------------------------------------------- 1 | import ModuleFactory from './webpack/lib/ModuleFactory'; 2 | import ContainerEntryModule from './ContainerEntryModule'; 3 | 4 | export default class ContainerEntryModuleFactory extends ModuleFactory { 5 | create({ dependencies: [dependency] }, callback) { 6 | callback(null, new ContainerEntryModule(dependency)); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ContainerExposedDependency.js: -------------------------------------------------------------------------------- 1 | import ModuleDependency from 'webpack/lib/dependencies/ModuleDependency'; 2 | 3 | export default class ContainerExposedDependency extends ModuleDependency { 4 | constructor(name, request) { 5 | super(request); 6 | this._name = name; 7 | } 8 | 9 | get exposedName() { 10 | return this._name; 11 | } 12 | 13 | getResourceIdentifier() { 14 | return `exposed dependency ${this._name}`; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ModuleFederationPlugin.js: -------------------------------------------------------------------------------- 1 | import JavascriptModulesPlugin from 'webpack/lib/JavascriptModulesPlugin'; 2 | import Template from 'webpack/lib/Template'; 3 | import propertyAccess from './webpack/lib/propertyAccess'; 4 | import validateOptions from 'schema-utils'; 5 | import ContainerExposedDependency from './ContainerExposedDependency'; 6 | import { ConcatSource } from 'webpack-sources'; 7 | import ContainerEntryDependency from './ContainerEntryDependency'; 8 | import ContainerEntryModuleFactory from './ContainerEntryModuleFactory'; 9 | import RemoteModule from './RemoteModule'; 10 | import SharedModule from './SharedModule'; 11 | import ContainerEntryModule from './ContainerEntryModule'; 12 | 13 | const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9]+ /; 14 | 15 | const globalType = 'global'; 16 | 17 | export default class ModuleFederationPlugin { 18 | static get name() { 19 | return ModuleFederationPlugin.constructor.name; 20 | } 21 | 22 | constructor(options) { 23 | const name = options.name ?? `remoteEntry`; 24 | validateOptions( 25 | { 26 | type: 'object', 27 | properties: { 28 | // shared: { 29 | // type: 'object', 30 | // }, 31 | exposes: { 32 | type: ['object', 'array'], 33 | }, 34 | 35 | remotes: { 36 | type: ['object', 'array'], 37 | }, 38 | name: { 39 | type: 'string', 40 | default: name, 41 | }, 42 | 43 | library: { 44 | type: 'object', 45 | properties: { 46 | name: { 47 | type: 'string' 48 | }, 49 | type: { 50 | type: 'string', 51 | default: 'var', 52 | enum: [ 53 | 'var', 54 | 'this', 55 | 'window', 56 | 'self', 57 | 'global', 58 | 'commonjs', 59 | 'commonjs2', 60 | 'amd', 61 | 'amd-require', 62 | 'umd', 63 | 'umd2', 64 | 'system', 65 | ] 66 | }, 67 | } 68 | }, 69 | 70 | filename: { 71 | anyOf: [{ type: 'string' }, { instanceof: 'Function' }], 72 | }, 73 | }, 74 | additionalProperties: false, 75 | }, 76 | options, 77 | { name: ModuleFederationPlugin.name }, 78 | ); 79 | 80 | this.options = { 81 | shared: options.shared ?? null, 82 | name, 83 | library: options.library ?? { 84 | type: 'global', 85 | name: name, 86 | }, 87 | filename: options.filename ?? undefined, // Undefined means, use the default behaviour 88 | exposes: options.exposes ?? {}, 89 | remotes: options.remotes ?? {}, 90 | }; 91 | 92 | let exposedMap = this.options.exposes || {}; 93 | 94 | if (Array.isArray(this.options.exposes)) { 95 | exposedMap = {}; 96 | for (const exp of this.options.exposes) { 97 | // TODO: Check if this regex handles all cases 98 | exposedMap[exp.replace(/(^(?:[^\w])+)/, '')] = exp; 99 | } 100 | } 101 | 102 | let sharedMap = this.options.shared || {}; 103 | 104 | if (Array.isArray(this.options.shared)) { 105 | sharedMap = {}; 106 | for (const exp of this.options.shared) { 107 | // TODO: Check if this regex handles all cases 108 | sharedMap[exp.replace(/(^(?:[^\w])+)/, '')] = exp; 109 | } 110 | } 111 | 112 | this.options.shared = sharedMap; 113 | this.options.exposes = exposedMap; 114 | 115 | if (!this.options.library.name) { 116 | this.options.library.name = name; 117 | } 118 | } 119 | 120 | apply(compiler) { 121 | if (compiler.options.optimization.runtimeChunk) { 122 | throw new Error( 123 | 'This plugin cannot integrate with RuntimeChunk plugin, please remote `optimization.runtimeChunk`.', 124 | ); 125 | } 126 | 127 | compiler.options.output.jsonpFunction = `${ 128 | compiler.options.output.jsonpFunction 129 | }${compiler.name ?? ''}${this.options.name}`; 130 | 131 | let deps = []; 132 | 133 | compiler.hooks.make.tapAsync( 134 | ModuleFederationPlugin.name, 135 | (compilation, callback) => { 136 | 137 | const asyncMap = { 138 | ...this.options.exposes, 139 | }; 140 | deps = Object.entries(asyncMap).map(([name, request], idx) => { 141 | const dep = new ContainerExposedDependency(name, request); 142 | dep.loc = { 143 | name, 144 | index: idx, 145 | }; 146 | return dep; 147 | }) 148 | 149 | 150 | compilation.addEntry( 151 | compilation.context, 152 | new ContainerEntryDependency( 153 | deps, 154 | this.options.name, 155 | ), 156 | this.options.name, 157 | callback, 158 | ); 159 | 160 | }, 161 | ); 162 | 163 | const handleRemote = (value, type, callback) => { 164 | /** @type {string} */ 165 | let externalConfig = value; 166 | // When no explicit type is specified, extract it from the externalConfig 167 | // if ( 168 | // type === undefined && 169 | // UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig) 170 | // ) { 171 | // const idx = externalConfig.indexOf(' '); 172 | // type = externalConfig.substr(0, idx); 173 | // externalConfig = externalConfig.substr(idx + 1); 174 | // } 175 | 176 | callback( 177 | null, 178 | new RemoteModule( 179 | externalConfig, 180 | type || globalType, 181 | value, 182 | this.options.remotes, 183 | this.options.shared, 184 | ), 185 | ); 186 | }; 187 | 188 | 189 | // const handleShared = (value, type, callback) => { 190 | 191 | // let externalConfig = value; 192 | 193 | // // When no explicit type is specified, extract it from the externalConfig 194 | // if ( 195 | // type === undefined && 196 | // UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig) 197 | // ) { 198 | // const idx = externalConfig.indexOf(' '); 199 | // type = externalConfig.substr(0, idx); 200 | // externalConfig = externalConfig.substr(idx + 1); 201 | // } 202 | 203 | // callback( 204 | // null, 205 | // new SharedModule( 206 | // externalConfig, 207 | // 'default', // TODO: remove hardcode 208 | // value, 209 | // ), 210 | // ); 211 | // }; 212 | 213 | 214 | compiler.hooks.normalModuleFactory.tap(ModuleFederationPlugin.name, (nmf) => { 215 | nmf.hooks.factory.tap(ModuleFederationPlugin.name, (fn) => { 216 | return (result, callback) => { 217 | const request = result?.request; 218 | const requestScope = result?.request?.split('/')?.shift?.(); 219 | 220 | if (this.options.remotes[requestScope]) { 221 | return handleRemote(result.request, null, callback); 222 | } 223 | // if (this.options.shared[request]) { 224 | // return handleShared( 225 | // this.options.shared[request], 226 | // undefined, 227 | // callback, 228 | // ); 229 | // } 230 | fn(result, (error, mod) => { 231 | 232 | callback(error, mod); 233 | }); 234 | }; 235 | }); 236 | 237 | // nmf.hooks.factory.tap(); 238 | }); 239 | 240 | 241 | // compiler.hooks.compile.tap( 242 | // ContainerPlugin.name, 243 | // () => { 244 | // new OverridablesPlugin( 245 | // this.options.shared, 246 | // ).apply(compiler); 247 | // }, 248 | // ); 249 | 250 | 251 | compiler.hooks.thisCompilation.tap( 252 | ModuleFederationPlugin.name, 253 | (compilation, { normalModuleFactory }) => { 254 | compilation.dependencyFactories.set( 255 | ContainerEntryDependency, 256 | new ContainerEntryModuleFactory(), 257 | ); 258 | 259 | compilation.dependencyFactories.set( 260 | ContainerExposedDependency, 261 | normalModuleFactory, 262 | ); 263 | 264 | compilation.hooks.afterOptimizeChunkAssets.tap( 265 | ModuleFederationPlugin.name, 266 | (chunks) => { 267 | 268 | for (let chunk of chunks) { 269 | if (!chunk.rendered) { 270 | // Skip already rendered (cached) chunks 271 | // to avoid rebuilding unchanged code. 272 | continue; 273 | } 274 | 275 | for (const fileName of chunk.files) { 276 | const source = compilation.assets[fileName]; 277 | 278 | let result = source; 279 | 280 | if (chunk.name === this.options.name) { 281 | const libName = Template.toIdentifier( 282 | compilation.getPath(this.options.library.name, { 283 | chunk, 284 | }), 285 | ); 286 | 287 | switch (this.options.library.type) { 288 | case 'var': { 289 | result = new ConcatSource(`var ${libName} =`, source); 290 | break; 291 | } 292 | case 'this': 293 | case 'window': 294 | case 'self': 295 | result = new ConcatSource( 296 | `${this.options.library.type}${propertyAccess([libName])} =`, 297 | source, 298 | ); 299 | break; 300 | case 'global': 301 | result = new ConcatSource( 302 | `${compiler.options.output.globalObject}${propertyAccess([ 303 | libName, 304 | ])} =`, 305 | source, 306 | ); 307 | break; 308 | case 'commonjs': 309 | case 'commonjs2': { 310 | result = new ConcatSource( 311 | `exports${propertyAccess([libName])} =`, 312 | source, 313 | ); 314 | break; 315 | 316 | } 317 | case 'amd': // TODO: Solve this? 318 | case 'amd-require': // TODO: Solve this? 319 | case 'umd': // TODO: Solve this? 320 | case 'umd2': // TODO: Solve this? 321 | case 'system': // TODO: Solve this? 322 | default: 323 | throw new Error( 324 | `${this.options.library.type} is not a valid Library target`, 325 | ); 326 | 327 | } 328 | 329 | } 330 | compilation.assets[fileName] = result; 331 | 332 | } 333 | } 334 | } 335 | ) 336 | 337 | 338 | compilation.hooks.afterChunks.tap(ModuleFederationPlugin.name, chunks => { 339 | for (const chunk of chunks) { 340 | if (chunk.name === this.options.name) { 341 | chunk.filenameTemplate = this.options.filename; 342 | } 343 | } 344 | }); 345 | }, 346 | ); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/RemoteModule.js: -------------------------------------------------------------------------------- 1 | const { OriginalSource, RawSource } = require('webpack-sources'); 2 | const Module = require('webpack/lib/Module'); 3 | const RuntimeGlobals = require('./webpack/lib/RuntimeGlobals'); 4 | const Template = require('webpack/lib/Template'); 5 | 6 | const getSourceForGlobalVariableExternal = ( 7 | variableName, 8 | type, 9 | requestScope, 10 | ) => { 11 | if (!Array.isArray(variableName)) { 12 | // make it an array as the look up works the same basically 13 | variableName = [variableName]; 14 | } 15 | 16 | const objectLookup = variableName.map(r => `${JSON.stringify(r)}`).join(''); 17 | 18 | // will output the following: 19 | // (function() { 20 | // module.exports = 21 | // typeof self["websiteTwo"] !== "undefined" ? self["websiteTwo"].get("Title") : 22 | // Promise.reject("Missing Remote Runtime: self[\"websiteTwo\"] cannot be found when trying to import \"Title\""); 23 | // }()); 24 | 25 | return Template.asString([ 26 | '(function() {', 27 | // `console.log('shared',${type}["${requestScope}"])`, 28 | 'module.exports =', 29 | `typeof ${type}["${requestScope}"] !== 'undefined' ? ${type}["${requestScope}"].get(${objectLookup}) : `, 30 | `Promise.reject('Missing Remote Runtime: ${type}["${requestScope}"] cannot be found when trying to import ${objectLookup}'); `, 31 | '}());', 32 | ]); 33 | }; 34 | 35 | /** 36 | * @param {string|string[]} moduleAndSpecifiers the module request 37 | * @param {string|string[]} requestScope the module request namespace scope 38 | * @returns {string} the generated source 39 | */ 40 | const getSourceForCommonJsExternal = (moduleAndSpecifiers, requestScope) => { 41 | if (!Array.isArray(moduleAndSpecifiers)) { 42 | // returns module.exports = require("websiteTwo").get("Title"); 43 | return `module.exports = require(${JSON.stringify( 44 | requestScope, 45 | )}).get(${JSON.stringify(moduleAndSpecifiers)});`; 46 | } 47 | 48 | const moduleName = moduleAndSpecifiers[0]; 49 | const objectLookup = moduleAndSpecifiers 50 | .slice(1) 51 | .map(r => `[${JSON.stringify(r)}]`) 52 | .join(''); 53 | 54 | // returns module.exports = require("websiteTwo").get("Title")["default"]; 55 | return `module.exports = require(${JSON.stringify( 56 | requestScope, 57 | )}).get(${JSON.stringify(moduleName)})${objectLookup};`; 58 | }; 59 | 60 | /** 61 | * @param {string} variableName the variable name to check 62 | * @param {string} request the request path 63 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 64 | * @returns {string} the generated source 65 | */ 66 | const checkExternalVariable = (variableName, request, runtimeTemplate) => { 67 | return `if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock( 68 | { request }, 69 | )} }\n`; 70 | }; 71 | 72 | /** 73 | * @param {string|number} id the module id 74 | * @param {boolean} optional true, if the module is optional 75 | * @param {string|string[]} request the request path 76 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 77 | * @returns {string} the generated source 78 | */ 79 | const getSourceForAmdOrUmdExternal = ( 80 | id, 81 | optional, 82 | request, 83 | runtimeTemplate, 84 | ) => { 85 | const externalVariable = `__WEBPACK_REMOTE_MODULE_${Template.toIdentifier( 86 | `${id}`, 87 | )}__`; 88 | const missingModuleError = optional 89 | ? checkExternalVariable( 90 | externalVariable, 91 | Array.isArray(request) ? request.join('.') : request, 92 | runtimeTemplate, 93 | ) 94 | : ''; 95 | return `${missingModuleError}module.exports = ${externalVariable};`; 96 | }; 97 | 98 | /** 99 | * @param {boolean} optional true, if the module is optional 100 | * @param {string|string[]} request the request path 101 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 102 | * @returns {string} the generated source 103 | */ 104 | const getSourceForDefaultCase = ( 105 | optional, 106 | request, 107 | runtimeTemplate, 108 | requestScope, 109 | ) => { 110 | if (!Array.isArray(request)) { 111 | // make it an array as the look up works the same basically 112 | request = [request]; 113 | } 114 | 115 | // TODO: use this for error handling 116 | const missingModuleError = optional 117 | ? checkExternalVariable(requestScope, request.join('.'), runtimeTemplate) 118 | : ''; 119 | 120 | // refactor conditional into checkExternalVariable 121 | return Template.asString([ 122 | 'module.exports = ', 123 | `typeof ${requestScope} !== 'undefined' ? ${requestScope}.get('${request}') : `, 124 | `Promise.reject("Missing Remote Runtime: ${requestScope} cannot be found when trying to import ${request}"); `, 125 | ]); 126 | }; 127 | 128 | const TYPES = new Set(['javascript']); 129 | const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); 130 | 131 | 132 | export default class RemoteModule extends Module { 133 | constructor(request, type, userRequest, remotes, shared) { 134 | super('javascript/dynamic', null); 135 | 136 | this.requestScope = request?.split('/')?.shift?.(); 137 | 138 | // Info from Factory 139 | /** @type {string | string[] | Record<string, string | string[]>} */ 140 | this.request = request?.split(`${this.requestScope}/`)?.[1]; 141 | 142 | if (remotes[this.requestScope]) { 143 | this.requestScope = remotes[this.requestScope]; 144 | } 145 | 146 | this.shared = shared; 147 | 148 | /** @type {string} */ 149 | this.remoteType = type; 150 | /** @type {string} */ 151 | this.userRequest = userRequest; 152 | 153 | } 154 | 155 | 156 | /** 157 | * @returns {Set<string>} types availiable (do not mutate) 158 | */ 159 | getSourceTypes() { 160 | return TYPES; 161 | } 162 | 163 | /** 164 | * @param {LibIdentOptions} options options 165 | * @returns {string | null} an identifier for library inclusion 166 | */ 167 | libIdent(options) { 168 | return this.userRequest; 169 | } 170 | 171 | /** 172 | * @param {Chunk} chunk the chunk which condition should be checked 173 | * @param {Compilation} compilation the compilation 174 | * @returns {boolean} true, if the chunk is ok for the module 175 | */ 176 | // chunkCondition(chunk, { chunkGraph }) { 177 | // return chunkGraph.getNumberOfEntryModules(chunk) > 0; 178 | // } 179 | 180 | /** 181 | * @returns {string} a unique identifier of the module 182 | */ 183 | identifier() { 184 | return `remote ${JSON.stringify(this.request)}`; 185 | } 186 | 187 | /** 188 | * @param {RequestShortener} requestShortener the request shortener 189 | * @returns {string} a user readable identifier of the module 190 | */ 191 | readableIdentifier(requestShortener) { 192 | return `remote ${JSON.stringify(this.request)}`; 193 | } 194 | 195 | /** 196 | * @param {NeedBuildContext} context context info 197 | * @param {function(WebpackError=, boolean=): void} callback callback function, returns true, if the module needs a rebuild 198 | * @returns {void} 199 | */ 200 | needBuild(context, callback) { 201 | return callback(null, !this.buildMeta); 202 | } 203 | 204 | /** 205 | * @param {WebpackOptions} options webpack options 206 | * @param {Compilation} compilation the compilation 207 | * @param {ResolverWithOptions} resolver the resolver 208 | * @param {InputFileSystem} fs the file system 209 | * @param {function(WebpackError=): void} callback callback function 210 | * @returns {void} 211 | */ 212 | build(options, compilation, resolver, fs, callback) { 213 | this.buildMeta = {}; 214 | this.buildInfo = { 215 | strict: true, 216 | }; 217 | 218 | callback(); 219 | } 220 | 221 | getSourceString(runtimeTemplate) { 222 | const request = 223 | typeof this.request === 'object' && !Array.isArray(this.request) 224 | ? this.request[this.remoteType] 225 | : this.request; 226 | switch (this.remoteType) { 227 | case 'this': 228 | case 'window': 229 | case 'self': 230 | return getSourceForGlobalVariableExternal( 231 | request, 232 | this.remoteType, 233 | this.requestScope, 234 | ); 235 | case 'global': 236 | return getSourceForGlobalVariableExternal( 237 | request, 238 | runtimeTemplate.outputOptions.globalObject, 239 | this.requestScope, 240 | ); 241 | case 'commonjs': 242 | case 'commonjs2': 243 | return getSourceForCommonJsExternal(request, this.requestScope); 244 | case 'amd': 245 | case 'amd-require': 246 | case 'umd': 247 | case 'umd2': 248 | case 'system': 249 | throw new Error( 250 | `${this.remoteType} is not supported with ContainerReferencePlugin`, 251 | ); 252 | return getSourceForAmdOrUmdExternal( 253 | chunkGraph.getModuleId(this), 254 | this.isOptional(moduleGraph), 255 | request, 256 | runtimeTemplate, 257 | this.requestScope, 258 | ); 259 | default: 260 | return getSourceForDefaultCase( 261 | this.isOptional(moduleGraph), 262 | request, 263 | runtimeTemplate, 264 | this.requestScope, 265 | ); 266 | } 267 | } 268 | 269 | 270 | /** 271 | * Get a list of runtime requirements 272 | * @param {SourceContext} context context for code generation 273 | * @returns {Iterable<string> | null} required runtime modules 274 | */ 275 | getRuntimeRequirements(context) { 276 | return [RuntimeGlobals.module, RuntimeGlobals.require]; 277 | } 278 | 279 | 280 | /** 281 | * @param {CodeGenerationContext} context context for code generation 282 | * @returns {CodeGenerationResult} result 283 | */ 284 | source(depTemplates, runtimeTemplate) { 285 | let sourceString = this.getSourceString( 286 | runtimeTemplate, 287 | ); 288 | 289 | // let sharedCode = `{`; 290 | // for (let key of Object.keys(this.shared)) { 291 | // sharedCode += `\n '${key}': function() {return __webpack_require__('${key}')},`; 292 | // } 293 | // sharedCode = sharedCode.replace(/,$/, '}'); 294 | 295 | // sourceString = `${this.requestScope}.override(${sharedCode});` + sourceString; 296 | 297 | let sources; 298 | if (this.useSourceMap) { 299 | sources = new OriginalSource(sourceString, this.identifier()); 300 | } else { 301 | sources = new RawSource(sourceString); 302 | } 303 | 304 | return sources; 305 | } 306 | 307 | /** 308 | * @param {string=} type the source type for which the size should be estimated 309 | * @returns {number} the estimated size of the module (must be non-zero) 310 | */ 311 | size(type) { 312 | return 42; 313 | } 314 | 315 | /** 316 | * @param {Hash} hash the hash used to track dependencies 317 | * @param {ChunkGraph} chunkGraph the chunk graph 318 | * @returns {void} 319 | */ 320 | updateHash(hash, chunkGraph) { 321 | // hash.update(this.remoteType); 322 | hash.update(JSON.stringify(this.request)); 323 | // hash.update( 324 | // JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph))) 325 | // ); 326 | super.updateHash(hash); 327 | } 328 | 329 | } 330 | 331 | -------------------------------------------------------------------------------- /src/SharedModule.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | 'use strict'; 7 | import AsyncDependenciesBlock from 'webpack/lib/AsyncDependenciesBlock'; 8 | import ContainerExposedDependency from './ContainerExposedDependency'; 9 | 10 | const { OriginalSource, RawSource } = require('webpack-sources'); 11 | const Module = require('webpack/lib/Module'); 12 | const RuntimeGlobals = require('./webpack/lib/RuntimeGlobals'); 13 | const Template = require('webpack/lib/Template'); 14 | // const makeSerializable = require('webpack/lib/util/makeSerializable'); 15 | 16 | /** @typedef {import("webpack-sources").Source} Source */ 17 | /** @typedef {import("../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ 18 | /** @typedef {import("./Chunk")} Chunk */ 19 | /** @typedef {import("./ChunkGraph")} ChunkGraph */ 20 | /** @typedef {import("./Compilation")} Compilation */ 21 | /** @typedef {import("./DependencyTemplates")} DependencyTemplates */ 22 | /** @typedef {import("./Module").CodeGenerationContext} CodeGenerationContext */ 23 | /** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ 24 | /** @typedef {import("./Module").LibIdentOptions} LibIdentOptions */ 25 | /** @typedef {import("./Module").NeedBuildContext} NeedBuildContext */ 26 | /** @typedef {import("./RequestShortener")} RequestShortener */ 27 | /** @typedef {import("./ResolverFactory").ResolverWithOptions} ResolverWithOptions */ 28 | /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ 29 | /** @typedef {import("./WebpackError")} WebpackError */ 30 | /** @typedef {import("./util/Hash")} Hash */ 31 | /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */ 32 | 33 | /** 34 | * @param {string|string[]} variableName the variable name or path 35 | * @param {string} type the module system 36 | * @returns {string} the generated source 37 | */ 38 | const getSourceForGlobalVariableExternal = (variableName, type) => { 39 | if (!Array.isArray(variableName)) { 40 | // make it an array as the look up works the same basically 41 | variableName = [variableName]; 42 | } 43 | 44 | // needed for e.g. window["some"]["thing"] 45 | const objectLookup = variableName.map(r => `[${JSON.stringify(r)}]`).join(''); 46 | return `(function() { module.exports = ${type}${objectLookup}; }());`; 47 | }; 48 | 49 | /** 50 | * @param {string|string[]} moduleAndSpecifiers the module request 51 | * @returns {string} the generated source 52 | */ 53 | const getSourceForCommonJsExternal = moduleAndSpecifiers => { 54 | if (!Array.isArray(moduleAndSpecifiers)) { 55 | return `module.exports = require(${JSON.stringify(moduleAndSpecifiers)});`; 56 | } 57 | const moduleName = moduleAndSpecifiers[0]; 58 | const objectLookup = moduleAndSpecifiers 59 | .slice(1) 60 | .map(r => `[${JSON.stringify(r)}]`) 61 | .join(''); 62 | return `module.exports = require(${JSON.stringify( 63 | moduleName, 64 | )})${objectLookup};`; 65 | }; 66 | 67 | /** 68 | * @param {string} variableName the variable name to check 69 | * @param {string} request the request path 70 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 71 | * @returns {string} the generated source 72 | */ 73 | const checkExternalVariable = (variableName, request, runtimeTemplate) => { 74 | return `if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock( 75 | { request }, 76 | )} }\n`; 77 | }; 78 | 79 | /** 80 | * @param {string|number} id the module id 81 | * @param {boolean} optional true, if the module is optional 82 | * @param {string|string[]} request the request path 83 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 84 | * @returns {string} the generated source 85 | */ 86 | const getSourceForAmdOrUmdExternal = ( 87 | id, 88 | optional, 89 | request, 90 | runtimeTemplate, 91 | ) => { 92 | const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier( 93 | `${id}`, 94 | )}__`; 95 | const missingModuleError = optional 96 | ? checkExternalVariable( 97 | externalVariable, 98 | Array.isArray(request) ? request.join('.') : request, 99 | runtimeTemplate, 100 | ) 101 | : ''; 102 | return `${missingModuleError}module.exports = ${externalVariable};`; 103 | }; 104 | 105 | /** 106 | * @param {boolean} optional true, if the module is optional 107 | * @param {string|string[]} request the request path 108 | * @param {RuntimeTemplate} runtimeTemplate the runtime template 109 | * @returns {string} the generated source 110 | */ 111 | const getSourceForDefaultCase = ( 112 | moduleId, 113 | optional, 114 | request, 115 | runtimeTemplate, 116 | ) => { 117 | if (!Array.isArray(request)) { 118 | // make it an array as the look up works the same basically 119 | request = [request]; 120 | } 121 | 122 | // TODO: use this for error handling 123 | const missingModuleError = optional 124 | ? checkExternalVariable(request.join('.'), runtimeTemplate) 125 | : ''; 126 | const requestScope = null; 127 | // refactor conditional into checkExternalVariable 128 | return Template.asString([ 129 | 'module.exports = ', 130 | `(typeof ${requestScope} !== 'undefined') ? typeof __webpack_require__.shared === 'function' ? (__webpack_require__.shared('${request}') || Promise.resolve(__webpack_require__('${request}'))) : Promise.resolve(__webpack_require__('${request}')):`, 131 | `Promise.reject("Missing Shared Module: ${requestScope} cannot be found when trying to override ${request}"); `, 132 | ]); 133 | }; 134 | 135 | const TYPES = new Set(['javascript']); 136 | const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); 137 | 138 | export default class SharedModule extends Module { 139 | constructor(request, type, userRequest) { 140 | super('javascript/dynamic', null); 141 | 142 | // Info from Factory 143 | /** @type {string | string[] | Record<string, string | string[]>} */ 144 | this.request = request; 145 | /** @type {string} */ 146 | this.externalType = type; 147 | /** @type {string} */ 148 | this.userRequest = userRequest; 149 | } 150 | 151 | /** 152 | * @returns {Set<string>} types availiable (do not mutate) 153 | */ 154 | getSourceTypes() { 155 | return TYPES; 156 | } 157 | 158 | /** 159 | * @param {LibIdentOptions} options options 160 | * @returns {string | null} an identifier for library inclusion 161 | */ 162 | libIdent(options) { 163 | return this.userRequest; 164 | } 165 | 166 | // /** 167 | // * @param {Chunk} chunk the chunk which condition should be checked 168 | // * @param {Compilation} compilation the compilation 169 | // * @returns {boolean} true, if the chunk is ok for the module 170 | // */ 171 | // chunkCondition(chunk, { chunkGraph }) { 172 | // return chunkGraph.getNumberOfEntryModules(chunk) > 0; 173 | // } 174 | 175 | /** 176 | * @returns {string} a unique identifier of the module 177 | */ 178 | identifier() { 179 | return 'shared ' + JSON.stringify(this.request); 180 | } 181 | 182 | /** 183 | * @param {RequestShortener} requestShortener the request shortener 184 | * @returns {string} a user readable identifier of the module 185 | */ 186 | readableIdentifier(requestShortener) { 187 | return 'shared ' + JSON.stringify(this.request); 188 | } 189 | 190 | /** 191 | * @param {NeedBuildContext} context context info 192 | * @param {function(WebpackError=, boolean=): void} callback callback function, returns true, if the module needs a rebuild 193 | * @returns {void} 194 | */ 195 | needBuild(context, callback) { 196 | return callback(null, !this.buildMeta); 197 | } 198 | 199 | 200 | build(options, compilation, resolver, fs, callback) { 201 | this.buildMeta = {}; 202 | this.buildInfo = { 203 | strict: true, 204 | exportsArgument: ['exports', '__webpack_require__'], 205 | }; 206 | 207 | callback(); 208 | } 209 | 210 | getSourceString(runtimeTemplate) { 211 | const request = 212 | typeof this.request === 'object' && !Array.isArray(this.request) 213 | ? this.request[this.externalType] 214 | : this.request; 215 | 216 | 217 | switch (this.externalType) { 218 | case 'this': 219 | case 'window': 220 | case 'self': 221 | return getSourceForGlobalVariableExternal(request, this.externalType); 222 | case 'global': 223 | return getSourceForGlobalVariableExternal( 224 | request, 225 | runtimeTemplate.outputOptions.globalObject, 226 | ); 227 | case 'commonjs': 228 | case 'commonjs2': 229 | return getSourceForCommonJsExternal(request); 230 | case 'amd': 231 | case 'amd-require': 232 | case 'umd': 233 | case 'umd2': 234 | case 'system': 235 | return getSourceForAmdOrUmdExternal( 236 | chunkGraph.getModuleId(this), 237 | this.isOptional(moduleGraph), 238 | request, 239 | runtimeTemplate, 240 | ); 241 | default: 242 | return getSourceForDefaultCase( 243 | this.index, 244 | null, 245 | request, 246 | runtimeTemplate, 247 | ); 248 | } 249 | } 250 | 251 | source(depTemplates, runtimeTemplate) { 252 | const sourceString = this.getSourceString( 253 | runtimeTemplate, 254 | ); 255 | let sources; 256 | if (this.useSourceMap) { 257 | sources = new OriginalSource(sourceString, this.identifier()); 258 | } else { 259 | sources = new RawSource(sourceString); 260 | } 261 | 262 | return sources; 263 | } 264 | 265 | /** 266 | * @param {string=} type the source type for which the size should be estimated 267 | * @returns {number} the estimated size of the module (must be non-zero) 268 | */ 269 | size(type) { 270 | return 42; 271 | } 272 | 273 | /** 274 | * @param {Hash} hash the hash used to track dependencies 275 | * @param {ChunkGraph} chunkGraph the chunk graph 276 | * @returns {void} 277 | */ 278 | updateHash(hash, chunkGraph) { 279 | hash.update(this.externalType); 280 | hash.update(JSON.stringify(this.request)); 281 | // hash.update( 282 | // JSON.stringify(Boolean(this.isOptional(chunkGraph.moduleGraph))), 283 | // ); 284 | super.updateHash(hash); 285 | } 286 | } 287 | 288 | // makeSerializable(SharedModule, 'webpack/lib/SharedModule'); 289 | -------------------------------------------------------------------------------- /src/SharedModuleFactoryPlugin.js: -------------------------------------------------------------------------------- 1 | import SharedModule from './SharedModule'; 2 | const UNSPECIFIED_EXTERNAL_TYPE_REGEXP = /^[a-z0-9]+ /; 3 | 4 | export default class SharedModuleFactoryPlugin { 5 | constructor(type, sharedModules) { 6 | this.shared = sharedModules; 7 | } 8 | 9 | apply(normalModuleFactory) { 10 | const globalType = this.remoteType; 11 | normalModuleFactory.hooks.factorize.tapAsync( 12 | 'SharedModuleFactoryPlugin', 13 | (data, callback) => { 14 | const { context } = data; 15 | const dependency = data.dependencies[0]; 16 | 17 | const handleShared = (value, type, callback) => { 18 | 19 | if (value === false) { 20 | // Not externals, fallback to original factory 21 | return callback(); 22 | } 23 | 24 | let externalConfig; 25 | if (value === true) { 26 | externalConfig = dependency.request; 27 | } else { 28 | externalConfig = value; 29 | } 30 | 31 | // When no explicit type is specified, extract it from the externalConfig 32 | if ( 33 | type === undefined && 34 | UNSPECIFIED_EXTERNAL_TYPE_REGEXP.test(externalConfig) 35 | ) { 36 | const idx = externalConfig.indexOf(' '); 37 | type = externalConfig.substr(0, idx); 38 | externalConfig = externalConfig.substr(idx + 1); 39 | } 40 | 41 | callback( 42 | null, 43 | new SharedModule( 44 | externalConfig, 45 | type || globalType, 46 | dependency.request, 47 | ), 48 | ); 49 | }; 50 | 51 | const handleExternals = (shared, callback) => { 52 | if (typeof shared === 'string') { 53 | if (shared[dependency.request]) { 54 | return handleShared(dependency.request, undefined, callback); 55 | } 56 | } else if (Array.isArray(shared)) { 57 | let i = 0; 58 | const next = () => { 59 | let asyncFlag; 60 | const handleExternalsAndCallback = (err, module) => { 61 | if (err) return callback(err); 62 | if (!module) { 63 | if (asyncFlag) { 64 | asyncFlag = false; 65 | return; 66 | } 67 | return next(); 68 | } 69 | callback(null, module); 70 | }; 71 | 72 | do { 73 | asyncFlag = true; 74 | if (i >= shared.length) return callback(); 75 | handleExternals(shared[i++], handleExternalsAndCallback); 76 | } while (!asyncFlag); 77 | asyncFlag = false; 78 | }; 79 | 80 | next(); 81 | return; 82 | } else if ( 83 | typeof shared === 'object' && 84 | Object.prototype.hasOwnProperty.call(shared, dependency.request) 85 | ) { 86 | if (shared[dependency.request]) { 87 | return handleShared( 88 | shared[dependency.request], 89 | undefined, 90 | callback, 91 | ); 92 | } 93 | } 94 | callback(); 95 | }; 96 | handleExternals(this.shared, callback); 97 | }, 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./ModuleFederationPlugin').default; -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-external-import", 3 | "description": "dynamic import() external urls!", 4 | "main": "dist/webpack-external-import.cjs.js", 5 | "module": "dist/webpack-external-import.esm.js", 6 | "version": "0.0.0-development", 7 | "author": "Zack Jackson <zack@ScriptedAlchemy.com> (https://github.com/ScriptedAlchemy)", 8 | "keywords": [ 9 | "import url", 10 | "dynamic imports url", 11 | "over the wire", 12 | "import() url", 13 | "external url import", 14 | "script injection", 15 | "webpack import url", 16 | "webpack", 17 | "manifest", 18 | "dynamic dll plugin", 19 | "dll plugin", 20 | "runtime" 21 | ], 22 | "license": "BSD-3-Clause", 23 | "scripts": { 24 | "semantic-release": "semantic-release -e semantic-release-monorepo" 25 | }, 26 | "dependencies": { 27 | "schema-utils": "2.6.1", 28 | "webpack-sources": "^1.4.3" 29 | }, 30 | "devDependencies": { 31 | "@semantic-release/changelog": "^5.0.0", 32 | "@semantic-release/git": "^9.0.0", 33 | "semantic-release": "^17.0.4", 34 | "semantic-release-monorepo": "^7.0.0" 35 | }, 36 | "peerDependencies": { 37 | }, 38 | "preconstruct": { 39 | "source": "index" 40 | }, 41 | "release": { 42 | "plugins": [ 43 | [ 44 | "@semantic-release/changelog", 45 | { 46 | "changelogFile": "CHANGELOG.md" 47 | } 48 | ], 49 | [ 50 | "@semantic-release/git", 51 | { 52 | "assets": [ 53 | "CHANGELOG.md" 54 | ] 55 | } 56 | ] 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/webpack/lib/ModuleFactory.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** @typedef {import("./Dependency")} Dependency */ 9 | /** @typedef {import("./Module")} Module */ 10 | 11 | /** 12 | * @typedef {Object} ModuleFactoryResult 13 | * @property {Module=} module the created module or unset if no module was created 14 | * @property {Set<string>=} fileDependencies 15 | * @property {Set<string>=} contextDependencies 16 | * @property {Set<string>=} missingDependencies 17 | */ 18 | 19 | /** 20 | * @typedef {Object} ModuleFactoryCreateDataContextInfo 21 | * @property {string} issuer 22 | * @property {string} compiler 23 | */ 24 | 25 | /** 26 | * @typedef {Object} ModuleFactoryCreateData 27 | * @property {ModuleFactoryCreateDataContextInfo} contextInfo 28 | * @property {any=} resolveOptions 29 | * @property {string} context 30 | * @property {Dependency[]} dependencies 31 | */ 32 | 33 | class ModuleFactory { 34 | /* istanbul ignore next */ 35 | /** 36 | * @abstract 37 | * @param {ModuleFactoryCreateData} data data object 38 | * @param {function(Error=, ModuleFactoryResult=): void} callback callback 39 | * @returns {void} 40 | */ 41 | create(data, callback) { 42 | const AbstractMethodError = require("webpack/lib/AbstractMethodError"); 43 | throw new AbstractMethodError(); 44 | } 45 | } 46 | 47 | module.exports = ModuleFactory; -------------------------------------------------------------------------------- /src/webpack/lib/RuntimeGlobals.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | /** 9 | * the internal require function 10 | */ 11 | exports.require = "__webpack_require__"; 12 | 13 | /** 14 | * access to properties of the internal require function/object 15 | */ 16 | exports.requireScope = "__webpack_require__.*"; 17 | 18 | /** 19 | * the internal exports object 20 | */ 21 | exports.exports = "__webpack_exports__"; 22 | 23 | /** 24 | * top-level this need to be the exports object 25 | */ 26 | exports.thisAsExports = "top-level-this-exports"; 27 | 28 | /** 29 | * top-level this need to be the exports object 30 | */ 31 | exports.returnExportsFromRuntime = "return-exports-from-runtime"; 32 | 33 | /** 34 | * the internal module object 35 | */ 36 | exports.module = "module"; 37 | 38 | /** 39 | * the internal module object 40 | */ 41 | exports.moduleId = "module.id"; 42 | 43 | /** 44 | * the internal module object 45 | */ 46 | exports.moduleLoaded = "module.loaded"; 47 | 48 | /** 49 | * the bundle public path 50 | */ 51 | exports.publicPath = "__webpack_require__.p"; 52 | 53 | /** 54 | * the module id of the entry point 55 | */ 56 | exports.entryModuleId = "__webpack_require__.s"; 57 | 58 | /** 59 | * the module cache 60 | */ 61 | exports.moduleCache = "__webpack_require__.c"; 62 | 63 | /** 64 | * the module functions 65 | */ 66 | exports.moduleFactories = "__webpack_require__.m"; 67 | 68 | /** 69 | * the module functions, with only write access 70 | */ 71 | exports.moduleFactoriesAddOnly = "__webpack_require__.m (add only)"; 72 | 73 | /** 74 | * the chunk ensure function 75 | */ 76 | exports.ensureChunk = "__webpack_require__.e"; 77 | 78 | /** 79 | * an object with handlers to ensure a chunk 80 | */ 81 | exports.ensureChunkHandlers = "__webpack_require__.f"; 82 | 83 | /** 84 | * a runtime requirement if ensureChunkHandlers should include loading of chunk needed for entries 85 | */ 86 | exports.ensureChunkIncludeEntries = "__webpack_require__.f (include entries)"; 87 | 88 | /** 89 | * the chunk prefetch function 90 | */ 91 | exports.prefetchChunk = "__webpack_require__.E"; 92 | 93 | /** 94 | * an object with handlers to prefetch a chunk 95 | */ 96 | exports.prefetchChunkHandlers = "__webpack_require__.F"; 97 | 98 | /** 99 | * the chunk preload function 100 | */ 101 | exports.preloadChunk = "__webpack_require__.G"; 102 | 103 | /** 104 | * an object with handlers to preload a chunk 105 | */ 106 | exports.preloadChunkHandlers = "__webpack_require__.H"; 107 | 108 | /** 109 | * the exported property define getters function 110 | */ 111 | exports.definePropertyGetters = "__webpack_require__.d"; 112 | 113 | /** 114 | * define compatibility on export 115 | */ 116 | exports.makeNamespaceObject = "__webpack_require__.r"; 117 | 118 | /** 119 | * create a fake namespace object 120 | */ 121 | exports.createFakeNamespaceObject = "__webpack_require__.t"; 122 | 123 | /** 124 | * compatibility get default export 125 | */ 126 | exports.compatGetDefaultExport = "__webpack_require__.n"; 127 | 128 | /** 129 | * harmony module decorator 130 | */ 131 | exports.harmonyModuleDecorator = "__webpack_require__.hmd"; 132 | 133 | /** 134 | * node.js module decorator 135 | */ 136 | exports.nodeModuleDecorator = "__webpack_require__.nmd"; 137 | 138 | /** 139 | * the webpack hash 140 | */ 141 | exports.getFullHash = "__webpack_require__.h"; 142 | 143 | /** 144 | * an object containing all installed WebAssembly.Instance export objects keyed by module id 145 | */ 146 | exports.wasmInstances = "__webpack_require__.w"; 147 | 148 | /** 149 | * instantiate a wasm instance from url/filename and importsObject 150 | */ 151 | exports.instantiateWasm = "__webpack_require__.v"; 152 | 153 | /** 154 | * the uncaught error handler for the webpack runtime 155 | */ 156 | exports.uncaughtErrorHandler = "__webpack_require__.oe"; 157 | 158 | /** 159 | * the script nonce 160 | */ 161 | exports.scriptNonce = "__webpack_require__.nc"; 162 | 163 | /** 164 | * the chunk name of the chunk with the runtime 165 | */ 166 | exports.chunkName = "__webpack_require__.cn"; 167 | 168 | /** 169 | * the filename of the script part of the chunk 170 | */ 171 | exports.getChunkScriptFilename = "__webpack_require__.u"; 172 | 173 | /** 174 | * the filename of the script part of the hot update chunk 175 | */ 176 | exports.getChunkUpdateScriptFilename = "__webpack_require__.hu"; 177 | 178 | /** 179 | * startup signal from runtime 180 | */ 181 | exports.startup = "__webpack_require__.x"; 182 | 183 | /** 184 | * startup signal from runtime 185 | */ 186 | exports.startupNoDefault = "__webpack_require__.x (no default handler)"; 187 | 188 | /** 189 | * interceptor for module executions 190 | */ 191 | exports.interceptModuleExecution = "__webpack_require__.i"; 192 | 193 | /** 194 | * the global object 195 | */ 196 | exports.global = "__webpack_require__.g"; 197 | 198 | /** 199 | * the filename of the HMR manifest 200 | */ 201 | exports.getUpdateManifestFilename = "__webpack_require__.hmrF"; 202 | 203 | /** 204 | * function downloading the update manifest 205 | */ 206 | exports.hmrDownloadManifest = "__webpack_require__.hmrM"; 207 | 208 | /** 209 | * array with handler functions to download chunk updates 210 | */ 211 | exports.hmrDownloadUpdateHandlers = "__webpack_require__.hmrC"; 212 | 213 | /** 214 | * object with all hmr module data for all modules 215 | */ 216 | exports.hmrModuleData = "__webpack_require__.hmrD"; 217 | 218 | /** 219 | * array with handler functions when a module should be invalidated 220 | */ 221 | exports.hmrInvalidateModuleHandlers = "__webpack_require__.hmrI"; 222 | 223 | /** 224 | * the AMD define function 225 | */ 226 | exports.amdDefine = "__webpack_require__.amdD"; 227 | 228 | /** 229 | * the AMD options 230 | */ 231 | exports.amdOptions = "__webpack_require__.amdO"; 232 | 233 | /** 234 | * the System polyfill object 235 | */ 236 | exports.system = "__webpack_require__.System"; 237 | 238 | /** 239 | * the shorthand for Object.prototype.hasOwnProperty 240 | * using of it decreases the compiled bundle size 241 | */ 242 | exports.hasOwnProperty = "__webpack_require__.o"; 243 | 244 | /** 245 | * the System.register context object 246 | */ 247 | exports.systemContext = "__webpack_require__.y"; -------------------------------------------------------------------------------- /src/webpack/lib/propertyAccess.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License http://www.opensource.org/licenses/mit-license.php 3 | Author Tobias Koppers @sokra 4 | */ 5 | 6 | "use strict"; 7 | 8 | const SAFE_IDENTIFIER = /^[_a-zA-Z$][_a-zA-z$0-9]*$/; 9 | 10 | const propertyAccess = (properties, start = 0) => { 11 | let str = ""; 12 | for (let i = start; i < properties.length; i++) { 13 | const p = properties[i]; 14 | if (`${+p}` === p) { 15 | str += `[${p}]`; 16 | } else if (SAFE_IDENTIFIER.test(p)) { 17 | str += `.${p}`; 18 | } else { 19 | str += `[${JSON.stringify(p)}]`; 20 | } 21 | } 22 | return str; 23 | }; 24 | 25 | module.exports = propertyAccess; 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | "allowJs": true, /* Allow javascript files to be compiled. */ 9 | "skipLibCheck": true, 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./lib", /* Redirect output structure to the directory. */ 17 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | 26 | /* Strict Type-Checking Options */ 27 | "strict": true, /* Enable all strict type-checking options. */ 28 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 29 | // "strictNullChecks": true, /* Enable strict null checks. */ 30 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 31 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 32 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 33 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 34 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 35 | 36 | /* Additional Checks */ 37 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 38 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 39 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 40 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 41 | 42 | /* Module Resolution Options */ 43 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 53 | 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | 60 | /* Experimental Options */ 61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 63 | 64 | /* Advanced Options */ 65 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 66 | }, 67 | "exclude": [ 68 | "__test__", 69 | "node_modules", 70 | "lib", 71 | "examples", 72 | "index.js", 73 | "babel.config.js", 74 | ] 75 | } 76 | --------------------------------------------------------------------------------