Merge branch 'master' into exports-field

# Conflicts:
#	lib/dependencies/ImportDependency.js
#	lib/sharing/ConsumeSharedPlugin.js
#	lib/sharing/ProvideSharedPlugin.js
#	types.d.ts
This commit is contained in:
Ivan Kopeykin 2020-06-16 21:16:41 +03:00
commit 198e51a485
163 changed files with 5456 additions and 1369 deletions

View File

@ -170,6 +170,9 @@
"prewalking", "prewalking",
"overridables", "overridables",
"overridable", "overridable",
"darkblue",
"darkgreen",
"darkred",
"webassemblyjs", "webassemblyjs",
"fsevents", "fsevents",
@ -209,5 +212,5 @@
"dependabot" "dependabot"
], ],
"ignoreRegExpList": ["/Author.+/", "/data:.*/", "/\"mappings\":\".+\"/"], "ignoreRegExpList": ["/Author.+/", "/data:.*/", "/\"mappings\":\".+\"/"],
"ignorePaths": ["**/dist/**"] "ignorePaths": ["**/dist/**", "examples/**/README.md"]
} }

View File

@ -151,7 +151,8 @@ export type ExternalsType =
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "import"; | "import"
| "script";
/** /**
* Filtering values. * Filtering values.
*/ */
@ -1970,7 +1971,7 @@ export interface EntryDescriptionNormalized {
/** /**
* Module(s) that are loaded upon startup. The last one is exported. * Module(s) that are loaded upon startup. The last one is exported.
*/ */
import: [string, ...string[]]; import?: [string, ...string[]];
/** /**
* Options for library. * Options for library.
*/ */

View File

@ -41,10 +41,14 @@ export interface SharedConfig {
* Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name. * Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name.
*/ */
import?: false | SharedItem; import?: false | SharedItem;
/**
* Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.
*/
packageName?: string;
/** /**
* Version requirement from module in share scope. * Version requirement from module in share scope.
*/ */
requiredVersion?: string | SharedVersionArray; requiredVersion?: false | string | SharedVersionArray;
/** /**
* Module is looked up under this key from the share scope. * Module is looked up under this key from the share scope.
*/ */

View File

@ -25,7 +25,8 @@ export type ExternalsType =
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "import"; | "import"
| "script";
/** /**
* Container locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location. * Container locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location.
*/ */

View File

@ -73,7 +73,8 @@ export type ExternalsType =
| "jsonp" | "jsonp"
| "system" | "system"
| "promise" | "promise"
| "import"; | "import"
| "script";
/** /**
* Container locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location. * Container locations and request scopes from which modules should be resolved and loaded at runtime. When provided, property name is used as request scope, otherwise request scope is automatically inferred from container location.
*/ */
@ -257,10 +258,14 @@ export interface SharedConfig {
* Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name. * Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name.
*/ */
import?: false | SharedItem; import?: false | SharedItem;
/**
* Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.
*/
packageName?: string;
/** /**
* Version requirement from module in share scope. * Version requirement from module in share scope.
*/ */
requiredVersion?: string | SharedVersionArray; requiredVersion?: false | string | SharedVersionArray;
/** /**
* Module is looked up under this key from the share scope. * Module is looked up under this key from the share scope.
*/ */

View File

@ -51,10 +51,14 @@ export interface ConsumesConfig {
* Fallback module if no shared module is found in share scope. Defaults to the property name. * Fallback module if no shared module is found in share scope. Defaults to the property name.
*/ */
import?: false | ConsumesItem; import?: false | ConsumesItem;
/**
* Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.
*/
packageName?: string;
/** /**
* Version requirement from module in share scope. * Version requirement from module in share scope.
*/ */
requiredVersion?: string | SharedVersionArray; requiredVersion?: false | string | SharedVersionArray;
/** /**
* Module is looked up under this key from the share scope. * Module is looked up under this key from the share scope.
*/ */

View File

@ -5,11 +5,11 @@
*/ */
/** /**
* Modules that should be provided as shared modules to the share scope. When provided, property name is used as share key, otherwise share key is automatically inferred from request. * Modules that should be provided as shared modules to the share scope. When provided, property name is used to match modules, otherwise this is automatically inferred from share key.
*/ */
export type Provides = (ProvidesItem | ProvidesObject)[] | ProvidesObject; export type Provides = (ProvidesItem | ProvidesObject)[] | ProvidesObject;
/** /**
* Request to a module that should be provided as shared module to the share scope. * Request to a module that should be provided as shared module to the share scope (will be resolved when relative).
*/ */
export type ProvidesItem = string; export type ProvidesItem = string;
/** /**
@ -19,7 +19,7 @@ export type SharedVersionArray = (number | string)[];
export interface ProvideSharedPluginOptions { export interface ProvideSharedPluginOptions {
/** /**
* Modules that should be provided as shared modules to the share scope. When provided, property name is used as share key, otherwise share key is automatically inferred from request. * Modules that should be provided as shared modules to the share scope. When provided, property name is used to match modules, otherwise this is automatically inferred from share key.
*/ */
provides: Provides; provides: Provides;
/** /**
@ -45,9 +45,9 @@ export interface ProvidesConfig {
*/ */
eager?: boolean; eager?: boolean;
/** /**
* Request to a module that should be provided as shared module to the share scope. * Key in the share scope under which the shared modules should be stored.
*/ */
import: ProvidesItem; shareKey?: string;
/** /**
* Share scope name. * Share scope name.
*/ */

View File

@ -51,10 +51,14 @@ export interface SharedConfig {
* Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name. * Provided module that should be provided to share scope. Also acts as fallback module if no shared module is found in share scope or version isn't valid. Defaults to the property name.
*/ */
import?: false | SharedItem; import?: false | SharedItem;
/**
* Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.
*/
packageName?: string;
/** /**
* Version requirement from module in share scope. * Version requirement from module in share scope.
*/ */
requiredVersion?: string | SharedVersionArray; requiredVersion?: false | string | SharedVersionArray;
/** /**
* Module is looked up under this key from the share scope. * Module is looked up under this key from the share scope.
*/ */

View File

@ -12,9 +12,11 @@ const async = require("neo-async");
const extraArgs = ""; const extraArgs = "";
const targetArgs = global.NO_TARGET_ARGS ? "" : " ./example.js -o dist/output.js "; const targetArgs = global.NO_TARGET_ARGS ? "" : "./example.js -o dist/output.js ";
const displayReasons = global.NO_REASONS ? "" : " --display-reasons --display-used-exports --display-provided-exports"; const displayReasons = global.NO_REASONS ? "" : "--display-reasons --display-used-exports --display-provided-exports";
const commonArgs = `--display-chunks --no-color --display-max-modules 99999 --display-origins --output-public-path "dist/" ${extraArgs} ${targetArgs}`; const statsArgs = global.NO_STATS_OPTIONS ? "" : "--display-chunks --display-max-modules 99999 --display-origins";
const publicPathArgs = global.NO_PUBLIC_PATH ? "" : '--output-public-path "dist/"';
const commonArgs = `--no-color ${statsArgs} ${publicPathArgs} ${extraArgs} ${targetArgs}`;
let readme = fs.readFileSync(require("path").join(process.cwd(), "template.md"), "utf-8"); let readme = fs.readFileSync(require("path").join(process.cwd(), "template.md"), "utf-8");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
global.NO_TARGET_ARGS = true;
global.NO_REASONS = true;
global.NO_STATS_OPTIONS = true;
global.NO_PUBLIC_PATH = true;
require("../build-common");

View File

@ -0,0 +1,83 @@
<html>
<head>
<style>
.spinner {
font-size: 10px;
margin: 50px auto;
text-indent: -9999em;
width: 11em;
height: 11em;
border-radius: 50%;
background: #595959;
background: linear-gradient(
to right,
#595959 10%,
rgba(89, 89, 89, 0) 42%
);
position: relative;
animation: spin 1.4s infinite linear;
transform: translateZ(0);
}
.spinner:before {
width: 50%;
height: 50%;
background: #595959;
border-radius: 100% 0 0 0;
position: absolute;
top: 0;
left: 0;
content: "";
}
.spinner:after {
background: white;
width: 75%;
height: 75%;
border-radius: 50%;
content: "";
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<!-- A spinner -->
<div class="spinner"></div>
<!-- This script only contains boostrapping logic -->
<!-- It will load all other scripts if neccessary -->
<script src="/dist/aaa/app.js" async></script>
<!-- These script tags are optional -->
<!-- They improve loading performance -->
<!-- Omitting them will add an additional round trip -->
<script src="/dist/bbb/mfeBBB.js" async></script>
<script src="/dist/ccc/mfeCCC.js" async></script>
<!-- All these scripts are pretty small ~5kb -->
<!-- For optimal performance they can be inlined -->
</body>
</html>

View File

@ -0,0 +1,18 @@
import React from "react";
import { formatRelative, subDays } from "date-fns";
// date-fns is a shared module, but used as usual
// exposing modules act as async boundary,
// so no additional async boundary need to be added here
// As data-fns is an shared module, it will be placed in a separate file
// It will be loaded in parallel to the code of this module
const Component = ({ locale }) => (
<div style={{ border: "5px solid darkblue" }}>
<p>I'm a Component exposed from container B!</p>
<p>
Using date-fn in Remote:{" "}
{formatRelative(subDays(new Date(), 2), new Date(), { locale })}
</p>
</div>
);
export default Component;

View File

@ -0,0 +1,13 @@
import React from "react";
import { formatRelative, subDays } from "date-fns";
const Component = ({ locale }) => (
<div style={{ border: "5px solid darkred" }}>
<p>I'm a Component exposed from container C!</p>
<p>
Using date-fn in Remote:{" "}
{formatRelative(subDays(new Date(), 3), new Date(), { locale })}
</p>
</div>
);
export default Component;

View File

@ -0,0 +1,11 @@
import React from "react";
import random from "lodash/random";
const Component = () => (
<div style={{ border: "5px solid darkgreen" }}>
<p>I'm a lazy Component exposed from container C!</p>
<p>I'm lazy loaded by the app and lazy load another component myself.</p>
<p>Using lodash in Remote: {random(0, 6)}</p>
</div>
);
export default Component;

View File

@ -0,0 +1,26 @@
import React from "react";
import ComponentB from "mfe-b/Component"; // <- these are remote modules,
import ComponentC from "mfe-c/Component"; // <- but they are used as usual packages
import { de } from "date-fns/locale";
// remote modules can also be used with import() which lazy loads them as usual
const ComponentD = React.lazy(() => import("mfe-c/Component2"));
const App = () => (
<article>
<header>
<h1>Hello World</h1>
</header>
<p>This component is from a remote container:</p>
<ComponentB locale={de} />
<p>And this component is from another remote container:</p>
<ComponentC locale={de} />
<React.Suspense fallback={<p>Lazy loading component...</p>}>
<p>
And this component is from this remote container too, but lazy loaded:
</p>
<ComponentD />
</React.Suspense>
</article>
);
export default App;

View File

@ -0,0 +1,11 @@
import ReactDom from "react-dom";
import React from "react"; // <- this is a shared module, but used as usual
import App from "./App";
// load app
const el = document.createElement("main");
ReactDom.render(<App />, el);
document.body.appendChild(el);
// remove spinner
document.body.removeChild(document.getElementsByClassName("spinner")[0]);

View File

@ -0,0 +1,13 @@
// Sharing modules requires that all remotes are initialized
// and can provide shared modules to the common scope
// As this is an async operation we need an async boundary (import())
// Using modules from remotes is also an async operation
// as chunks need to be loaded for the code of the remote module
// This also requires an async boundary (import())
// At this point shared modules initialized and remote modules are loaded
import("./bootstrap");
// It's possible to place more code here to do stuff on page init
// but it can't use any of the shared modules or remote modules.

View File

@ -0,0 +1,67 @@
# webpack.config.js
```javascript
_{{webpack.config.js}}_
```
# src/index.js
```javascript
_{{src/index.js}}_
```
# src/bootstrap.js
```jsx
_{{src/bootstrap.js}}_
```
# src/App.js
```jsx
_{{src/App.js}}_
```
# index.html
```html
_{{index.html}}_
```
# src-b/Component.js
```jsx
_{{src-b/Component.js}}_
```
# dist/aaa/app.js
```javascript
_{{dist/aaa/app.js}}_
```
# dist/bbb/mfeBBB.js
```javascript
_{{dist/bbb/mfeBBB.js}}_
```
# dist/ccc/mfeCCC.js
```javascript
_{{dist/ccc/mfeCCC.js}}_
```
# Info
## Unoptimized
```
_{{stdout}}_
```
## Production mode
```
_{{production:stdout}}_
```

View File

@ -0,0 +1,151 @@
const path = require("path");
const { ModuleFederationPlugin } = require("../../").container;
const rules = [
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
use: {
loader: "babel-loader",
options: {
presets: ["@babel/react"]
}
}
}
];
const optimization = {
chunkIds: "named", // for this example only: readable filenames in production too
nodeEnv: "production" // for this example only: always production version of react
};
const stats = {
chunks: true,
modules: false,
chunkModules: false,
chunkRootModules: true,
chunkOrigins: true
};
module.exports = (env = "development") => [
// For this example we have 3 configs in a single file
// In practice you probably would have separate config
// maybe even separate repos for each build.
// For Module Federation there is not compile-time dependency
// between the builds.
// Each one can have different config options.
{
name: "app",
mode: env,
entry: {
app: "./src/index.js"
},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist/aaa"),
publicPath: "dist/aaa/",
// Each build needs a unique name
// to avoid runtime collisions
// The default uses "name" from package.json
uniqueName: "module-federation-aaa"
},
module: { rules },
optimization,
plugins: [
new ModuleFederationPlugin({
// List of remotes with URLs
remotes: {
"mfe-b": "mfeBBB@/dist/bbb/mfeBBB.js",
"mfe-c": "mfeCCC@/dist/ccc/mfeCCC.js"
},
// list of shared modules with optional options
shared: {
// specifying a module request as shared module
// will provide all used modules matching this name (version from package.json)
// and consume shared modules in the version specified in dependencies from package.json
// (or in dev/peer/optionalDependencies)
// So it use the highest available version of this package matching the version requirement
// from package.json, while providing it's own version to others.
react: {
singleton: true // make sure only a single react module is used
}
}
})
],
stats
},
{
name: "mfe-b",
mode: env,
entry: {},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist/bbb"),
publicPath: "dist/bbb/",
uniqueName: "module-federation-bbb"
},
module: { rules },
optimization,
plugins: [
new ModuleFederationPlugin({
// A unique name
name: "mfeBBB",
// List of exposed modules
exposes: {
"./Component": "./src-b/Component"
},
// list of shared modules
shared: [
// date-fns is shared with the other remote, app doesn't know about that
"date-fns",
{
react: {
singleton: true // must be specified in each config
}
}
]
})
],
stats
},
{
name: "mfe-c",
mode: env,
entry: {},
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist/ccc"),
publicPath: "dist/ccc/",
uniqueName: "module-federation-ccc"
},
module: { rules },
optimization,
plugins: [
new ModuleFederationPlugin({
name: "mfeCCC",
exposes: {
"./Component": "./src-c/Component",
"./Component2": "./src-c/LazyComponent"
},
shared: [
// All (used) requests within lodash are shared.
"lodash/",
"date-fns",
{
react: {
// Do not load our own version.
// There must be a valid shared module available at runtime.
// This improves build time as this module doesn't need to be compiled,
// but it opts-out of possible fallbacks and runtime version upgrade.
import: false,
singleton: true
}
}
]
})
],
stats
}
];

View File

@ -81,17 +81,6 @@ class BannerPlugin {
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
}, },
() => { () => {
let files;
if (options.entryOnly) {
files = new Set();
for (const chunk of compilation.chunks) {
if (chunk.canBeInitial()) {
for (const file of chunk.files) files.add(file);
}
}
} else {
files = Object.keys(compilation.assets);
}
for (const chunk of compilation.chunks) { for (const chunk of compilation.chunks) {
if (options.entryOnly && !chunk.canBeInitial()) { if (options.entryOnly && !chunk.canBeInitial()) {
continue; continue;
@ -102,11 +91,9 @@ class BannerPlugin {
continue; continue;
} }
let filename = file;
const data = { const data = {
chunk, chunk,
filename filename: file
}; };
const comment = compilation.getPath(banner, data); const comment = compilation.getPath(banner, data);

View File

@ -77,6 +77,7 @@ const {
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */
/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("./DependencyTemplate")} DependencyTemplate */ /** @typedef {import("./DependencyTemplate")} DependencyTemplate */
/** @typedef {import("./Module")} Module */ /** @typedef {import("./Module")} Module */
/** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */ /** @typedef {import("./Module").CodeGenerationResult} CodeGenerationResult */
@ -151,7 +152,7 @@ const {
/** /**
* @typedef {Object} EntryData * @typedef {Object} EntryData
* @property {Dependency[]} dependencies dependencies of the entrypoint that should be evaluated at startup * @property {Dependency[]} dependencies dependencies of the entrypoint that should be evaluated at startup
* @property {Dependency[]} includeDependencies dependencies of the entrypoint that should be included by not evaluated * @property {Dependency[]} includeDependencies dependencies of the entrypoint that should be included but not evaluated
* @property {EntryOptions} options options of the entrypoint * @property {EntryOptions} options options of the entrypoint
*/ */
@ -357,7 +358,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
/** @type {SyncHook<[Dependency, EntryOptions, Module]>} */ /** @type {SyncHook<[Dependency, EntryOptions, Module]>} */
succeedEntry: new SyncHook(["entry", "options", "module"]), succeedEntry: new SyncHook(["entry", "options", "module"]),
/** @type {SyncWaterfallHook<[string[][], Dependency]>} */ /** @type {SyncWaterfallHook<[(string[] | ReferencedExport)[], Dependency]>} */
dependencyReferencedExports: new SyncWaterfallHook([ dependencyReferencedExports: new SyncWaterfallHook([
"referencedExports", "referencedExports",
"dependency" "dependency"
@ -1582,6 +1583,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
} else { } else {
entryData[target].push(entry); entryData[target].push(entry);
for (const key of Object.keys(options)) { for (const key of Object.keys(options)) {
if (options[key] === undefined) continue;
if (entryData.options[key] === options[key]) continue; if (entryData.options[key] === options[key]) continue;
if (entryData.options[key] === undefined) { if (entryData.options[key] === undefined) {
entryData.options[key] = options[key]; entryData.options[key] = options[key];
@ -2250,7 +2252,7 @@ Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*
/** /**
* @param {Dependency} dependency the dependency * @param {Dependency} dependency the dependency
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getDependencyReferencedExports(dependency) { getDependencyReferencedExports(dependency) {
const referencedExports = dependency.getReferencedExports(this.moduleGraph); const referencedExports = dependency.getReferencedExports(this.moduleGraph);

View File

@ -137,6 +137,8 @@ class Compiler {
compile: new SyncHook(["params"]), compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<[Compilation], Module>} */ /** @type {AsyncParallelHook<[Compilation], Module>} */
make: new AsyncParallelHook(["compilation"]), make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncParallelHook<[Compilation], Module>} */
finishMake: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[Compilation]>} */ /** @type {AsyncSeriesHook<[Compilation]>} */
afterCompile: new AsyncSeriesHook(["compilation"]), afterCompile: new AsyncSeriesHook(["compilation"]),
@ -913,23 +915,29 @@ class Compiler {
logger.timeEnd("make hook"); logger.timeEnd("make hook");
if (err) return callback(err); if (err) return callback(err);
process.nextTick(() => { logger.time("finish make hook");
logger.time("finish compilation"); this.hooks.finishMake.callAsync(compilation, err => {
compilation.finish(err => { logger.timeEnd("finish make hook");
logger.timeEnd("finish compilation"); if (err) return callback(err);
if (err) return callback(err);
logger.time("seal compilation"); process.nextTick(() => {
compilation.seal(err => { logger.time("finish compilation");
logger.timeEnd("seal compilation"); compilation.finish(err => {
logger.timeEnd("finish compilation");
if (err) return callback(err); if (err) return callback(err);
logger.time("afterCompile hook"); logger.time("seal compilation");
this.hooks.afterCompile.callAsync(compilation, err => { compilation.seal(err => {
logger.timeEnd("afterCompile hook"); logger.timeEnd("seal compilation");
if (err) return callback(err); if (err) return callback(err);
return callback(null, compilation); logger.time("afterCompile hook");
this.hooks.afterCompile.callAsync(compilation, err => {
logger.timeEnd("afterCompile hook");
if (err) return callback(err);
return callback(null, compilation);
});
}); });
}); });
}); });

View File

@ -52,6 +52,7 @@ const makeSerializable = require("./util/makeSerializable");
* @property {RegExp=} include * @property {RegExp=} include
* @property {RegExp=} exclude * @property {RegExp=} exclude
* @property {RawChunkGroupOptions=} groupOptions * @property {RawChunkGroupOptions=} groupOptions
* @property {string[][]=} referencedExports exports referenced from modules (won't be mangled)
*/ */
/** /**
@ -66,13 +67,13 @@ const makeSerializable = require("./util/makeSerializable");
/** /**
* @callback ResolveDependenciesCallback * @callback ResolveDependenciesCallback
* @param {Error=} err * @param {Error=} err
* @param {ContextElementDependency[]} dependencies * @param {ContextElementDependency[]=} dependencies
*/ */
/** /**
* @callback ResolveDependencies * @callback ResolveDependencies
* @param {TODO} fs * @param {InputFileSystem} fs
* @param {TODO} options * @param {ContextModuleOptions} options
* @param {ResolveDependenciesCallback} callback * @param {ResolveDependenciesCallback} callback
*/ */
@ -116,6 +117,7 @@ class ContextModule extends Module {
exclude: options.exclude, exclude: options.exclude,
chunkName: options.chunkName, chunkName: options.chunkName,
groupOptions: options.groupOptions, groupOptions: options.groupOptions,
referencedExports: options.referencedExports,
namespaceObject: options.namespaceObject namespaceObject: options.namespaceObject
}; };
if (options.resolveOptions !== undefined) { if (options.resolveOptions !== undefined) {
@ -183,6 +185,11 @@ class ContextModule extends Module {
if (this.options.exclude) { if (this.options.exclude) {
identifier += `|exclude: ${this.options.exclude}`; identifier += `|exclude: ${this.options.exclude}`;
} }
if (this.options.referencedExports) {
identifier += `|referencedExports: ${JSON.stringify(
this.options.referencedExports
)}`;
}
if (this.options.chunkName) { if (this.options.chunkName) {
identifier += `|chunkName: ${this.options.chunkName}`; identifier += `|chunkName: ${this.options.chunkName}`;
} }
@ -234,6 +241,11 @@ class ContextModule extends Module {
if (this.options.exclude) { if (this.options.exclude) {
identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`; identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`;
} }
if (this.options.referencedExports) {
identifier += ` referencedExports: ${this.options.referencedExports
.map(e => e.join("."))
.join(", ")}`;
}
if (this.options.chunkName) { if (this.options.chunkName) {
identifier += ` chunkName: ${this.options.chunkName}`; identifier += ` chunkName: ${this.options.chunkName}`;
} }
@ -284,6 +296,11 @@ class ContextModule extends Module {
if (this.options.exclude) { if (this.options.exclude) {
identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`; identifier += ` exclude: ${this.prettyRegExp(this.options.exclude + "")}`;
} }
if (this.options.referencedExports) {
identifier += ` referencedExports: ${this.options.referencedExports
.map(e => e.join("."))
.join(", ")}`;
}
return identifier; return identifier;
} }

View File

@ -13,11 +13,14 @@ const ContextElementDependency = require("./dependencies/ContextElementDependenc
const { cachedSetProperty } = require("./util/cleverMerge"); const { cachedSetProperty } = require("./util/cleverMerge");
const { join } = require("./util/fs"); const { join } = require("./util/fs");
/** @typedef {import("./ContextModule").ContextModuleOptions} ContextModuleOptions */
/** @typedef {import("./ContextModule").ResolveDependenciesCallback} ResolveDependenciesCallback */
/** @typedef {import("./Module")} Module */ /** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */ /** @typedef {import("./ModuleFactory").ModuleFactoryCreateData} ModuleFactoryCreateData */
/** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */ /** @typedef {import("./ModuleFactory").ModuleFactoryResult} ModuleFactoryResult */
/** @typedef {import("./ResolverFactory")} ResolverFactory */ /** @typedef {import("./ResolverFactory")} ResolverFactory */
/** @typedef {import("./dependencies/ContextDependency")} ContextDependency */ /** @typedef {import("./dependencies/ContextDependency")} ContextDependency */
/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
const EMPTY_RESOLVE_OPTIONS = {}; const EMPTY_RESOLVE_OPTIONS = {};
@ -218,14 +221,23 @@ module.exports = class ContextModuleFactory extends ModuleFactory {
); );
} }
/**
* @param {InputFileSystem} fs file system
* @param {ContextModuleOptions} options options
* @param {ResolveDependenciesCallback} callback callback function
* @returns {void}
*/
resolveDependencies(fs, options, callback) { resolveDependencies(fs, options, callback) {
const cmf = this; const cmf = this;
let resource = options.resource; const {
let resourceQuery = options.resourceQuery; resource,
let recursive = options.recursive; resourceQuery,
let regExp = options.regExp; recursive,
let include = options.include; regExp,
let exclude = options.exclude; include,
exclude,
referencedExports
} = options;
if (!regExp || !resource) return callback(null, []); if (!regExp || !resource) return callback(null, []);
const addDirectory = (directory, callback) => { const addDirectory = (directory, callback) => {
@ -274,7 +286,8 @@ module.exports = class ContextModuleFactory extends ModuleFactory {
.map(obj => { .map(obj => {
const dep = new ContextElementDependency( const dep = new ContextElementDependency(
obj.request + resourceQuery, obj.request + resourceQuery,
obj.request obj.request,
referencedExports
); );
dep.optional = true; dep.optional = true;
return dep; return dep;

View File

@ -10,6 +10,7 @@ const Module = require("./Module");
const RuntimeGlobals = require("./RuntimeGlobals"); const RuntimeGlobals = require("./RuntimeGlobals");
const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency"); const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
const StaticExportsDependency = require("./dependencies/StaticExportsDependency"); const StaticExportsDependency = require("./dependencies/StaticExportsDependency");
const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ /** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
@ -182,6 +183,48 @@ class DelegatedModule extends Module {
hash.update(JSON.stringify(this.request)); hash.update(JSON.stringify(this.request));
super.updateHash(hash, chunkGraph); super.updateHash(hash, chunkGraph);
} }
serialize(context) {
const { write } = context;
// constructor
write(this.sourceRequest);
write(this.delegateData);
write(this.delegationType);
write(this.userRequest);
write(this.originalRequest);
super.serialize(context);
}
static deserialize(context) {
const { read } = context;
const obj = new DelegatedModule(
read(), // sourceRequest
read(), // delegateData
read(), // delegationType
read(), // userRequest
read() // originalRequest
);
obj.deserialize(context);
return obj;
}
/**
* Assuming this module is in the cache. Update the (cached) module with
* the fresh module from the factory. Usually updates internal references
* and properties.
* @param {Module} module fresh module
* @returns {void}
*/
updateCacheModule(module) {
super.updateCacheModule(module);
const m = /** @type {DelegatedModule} */ (module);
this.delegationType = m.delegationType;
this.userRequest = m.userRequest;
this.originalRequest = m.originalRequest;
this.delegateData = m.delegateData;
}
} }
makeSerializable(DelegatedModule, "webpack/lib/DelegatedModule");
module.exports = DelegatedModule; module.exports = DelegatedModule;

View File

@ -52,6 +52,12 @@
* @property {Module[]=} dependencies module on which the result depends on * @property {Module[]=} dependencies module on which the result depends on
*/ */
/**
* @typedef {Object} ReferencedExport
* @property {string[]} name name of the referenced export
* @property {boolean=} canMangle when false, referenced export can not be mangled, defaults to true
*/
class Dependency { class Dependency {
constructor() { constructor() {
// TODO check if this can be moved into ModuleDependency // TODO check if this can be moved into ModuleDependency
@ -100,7 +106,7 @@ class Dependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return Dependency.EXPORTS_OBJECT_REFERENCED; return Dependency.EXPORTS_OBJECT_REFERENCED;

View File

@ -8,6 +8,7 @@
const { RawSource } = require("webpack-sources"); const { RawSource } = require("webpack-sources");
const Module = require("./Module"); const Module = require("./Module");
const RuntimeGlobals = require("./RuntimeGlobals"); const RuntimeGlobals = require("./RuntimeGlobals");
const makeSerializable = require("./util/makeSerializable");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */ /** @typedef {import("../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
@ -119,6 +120,30 @@ class DllModule extends Module {
hash.update(this.name || ""); hash.update(this.name || "");
super.updateHash(hash, chunkGraph); super.updateHash(hash, chunkGraph);
} }
serialize(context) {
context.write(this.name);
super.serialize(context);
}
deserialize(context) {
this.name = context.read();
super.deserialize(context);
}
/**
* Assuming this module is in the cache. Update the (cached) module with
* the fresh module from the factory. Usually updates internal references
* and properties.
* @param {Module} module fresh module
* @returns {void}
*/
updateCacheModule(module) {
super.updateCacheModule(module);
this.dependencies = module.dependencies;
}
} }
makeSerializable(DllModule, "webpack/lib/DllModule");
module.exports = DllModule; module.exports = DllModule;

View File

@ -86,6 +86,42 @@ const getSourceForImportExternal = (moduleAndSpecifiers, runtimeTemplate) => {
)});`; )});`;
}; };
/**
* @param {string|string[]} urlAndGlobal the script request
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @returns {string} the generated source
*/
const getSourceForScriptExternal = (urlAndGlobal, runtimeTemplate) => {
if (typeof urlAndGlobal === "string") {
urlAndGlobal = urlAndGlobal.split("@").reverse();
}
const url = urlAndGlobal[0];
const globalName = urlAndGlobal[1];
return Template.asString([
"var error = new Error();",
`module.exports = new Promise(${runtimeTemplate.basicFunction(
"resolve, reject",
[
`if(typeof ${globalName} !== "undefined") return resolve();`,
`${RuntimeGlobals.loadScript}(${JSON.stringify(
url
)}, ${runtimeTemplate.basicFunction("event", [
`if(typeof ${globalName} !== "undefined") return resolve();`,
"var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
"var realSrc = event && event.target && event.target.src;",
"error.message = 'Loading script failed.\\n(' + errorType + ': ' + realSrc + ')';",
"error.name = 'ScriptExternalLoadError';",
"error.type = errorType;",
"error.request = realSrc;",
"reject(error);"
])}, ${JSON.stringify(globalName)});`
]
)}).then(${runtimeTemplate.returningFunction(
`${globalName}${propertyAccess(urlAndGlobal, 2)}`
)})`
]);
};
/** /**
* @param {string} variableName the variable name to check * @param {string} variableName the variable name to check
* @param {string} request the request path * @param {string} request the request path
@ -146,6 +182,10 @@ const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
const TYPES = new Set(["javascript"]); const TYPES = new Set(["javascript"]);
const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]); const RUNTIME_REQUIREMENTS = new Set([RuntimeGlobals.module]);
const RUNTIME_REQUIREMENTS_FOR_SCRIPT = new Set([
RuntimeGlobals.module,
RuntimeGlobals.loadScript
]);
class ExternalModule extends Module { class ExternalModule extends Module {
constructor(request, type, userRequest) { constructor(request, type, userRequest) {
@ -239,6 +279,9 @@ class ExternalModule extends Module {
if (!Array.isArray(this.request) || this.request.length === 1) if (!Array.isArray(this.request) || this.request.length === 1)
this.buildMeta.exportsType = "namespace"; this.buildMeta.exportsType = "namespace";
break; break;
case "script":
this.buildMeta.async = true;
break;
} }
callback(); callback();
} }
@ -276,6 +319,8 @@ class ExternalModule extends Module {
); );
case "import": case "import":
return getSourceForImportExternal(request, runtimeTemplate); return getSourceForImportExternal(request, runtimeTemplate);
case "script":
return getSourceForScriptExternal(request, runtimeTemplate);
case "module": case "module":
throw new Error("Module external type is not implemented yet"); throw new Error("Module external type is not implemented yet");
case "var": case "var":
@ -313,7 +358,13 @@ class ExternalModule extends Module {
sources.set("javascript", new RawSource(sourceString)); sources.set("javascript", new RawSource(sourceString));
} }
return { sources, runtimeRequirements: RUNTIME_REQUIREMENTS }; return {
sources,
runtimeRequirements:
this.externalType === "script"
? RUNTIME_REQUIREMENTS_FOR_SCRIPT
: RUNTIME_REQUIREMENTS
};
} }
/** /**

View File

@ -13,6 +13,7 @@ const Queue = require("./util/Queue");
/** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("./Module")} Module */ /** @typedef {import("./Module")} Module */
/** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */ /** @typedef {import("./ModuleGraph").ExportsInfo} ExportsInfo */
@ -39,15 +40,22 @@ class FlagDependencyUsagePlugin {
const exportInfoToModuleMap = new Map(); const exportInfoToModuleMap = new Map();
/** /**
* @typedef {string[]} StringArray
* @param {Module} module module to process * @param {Module} module module to process
* @param {StringArray[]} usedExports list of used exports * @param {(string[] | ReferencedExport)[]} usedExports list of used exports
* @returns {void} * @returns {void}
*/ */
const processModule = (module, usedExports) => { const processModule = (module, usedExports) => {
const exportsInfo = moduleGraph.getExportsInfo(module); const exportsInfo = moduleGraph.getExportsInfo(module);
if (usedExports.length > 0) { if (usedExports.length > 0) {
for (let usedExport of usedExports) { for (const usedExportInfo of usedExports) {
let usedExport;
let canMangle = true;
if (Array.isArray(usedExportInfo)) {
usedExport = usedExportInfo;
} else {
usedExport = usedExportInfo.name;
canMangle = usedExportInfo.canMangle !== false;
}
if (usedExport.length === 0) { if (usedExport.length === 0) {
if (exportsInfo.setUsedInUnknownWay()) { if (exportsInfo.setUsedInUnknownWay()) {
queue.enqueue(module); queue.enqueue(module);
@ -55,10 +63,12 @@ class FlagDependencyUsagePlugin {
} else { } else {
let currentExportsInfo = exportsInfo; let currentExportsInfo = exportsInfo;
for (let i = 0; i < usedExport.length; i++) { for (let i = 0; i < usedExport.length; i++) {
const exportName = usedExport[i];
const exportInfo = currentExportsInfo.getExportInfo( const exportInfo = currentExportsInfo.getExportInfo(
exportName usedExport[i]
); );
if (canMangle === false) {
exportInfo.canMangleUse = false;
}
const lastOne = i === usedExport.length - 1; const lastOne = i === usedExport.length - 1;
if (!lastOne) { if (!lastOne) {
const nestedInfo = exportInfo.getNestedExportsInfo(); const nestedInfo = exportInfo.getNestedExportsInfo();

View File

@ -147,8 +147,8 @@ class NormalModuleFactory extends ModuleFactory {
beforeResolve: new AsyncSeriesBailHook(["resolveData"]), beforeResolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */ /** @type {AsyncSeriesBailHook<[ResolveData], TODO>} */
afterResolve: new AsyncSeriesBailHook(["resolveData"]), afterResolve: new AsyncSeriesBailHook(["resolveData"]),
/** @type {SyncBailHook<[ResolveData], TODO>} */ /** @type {AsyncSeriesBailHook<[ResolveData["createData"], ResolveData], TODO>} */
createModule: new SyncBailHook(["resolveData"]), createModule: new AsyncSeriesBailHook(["createData", "resolveData"]),
/** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], TODO>} */ /** @type {SyncWaterfallHook<[Module, ResolveData["createData"], ResolveData], TODO>} */
module: new SyncWaterfallHook(["module", "createData", "resolveData"]), module: new SyncWaterfallHook(["module", "createData", "resolveData"]),
createParser: new HookMap(() => new SyncBailHook(["parserOptions"])), createParser: new HookMap(() => new SyncBailHook(["parserOptions"])),
@ -216,22 +216,27 @@ class NormalModuleFactory extends ModuleFactory {
const createData = resolveData.createData; const createData = resolveData.createData;
let createdModule = this.hooks.createModule.call(createData); this.hooks.createModule.callAsync(
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
createdModule = new NormalModule(createData);
}
createdModule = this.hooks.module.call(
createdModule,
createData, createData,
resolveData resolveData,
); (err, createdModule) => {
if (!createdModule) {
if (!resolveData.request) {
return callback(new Error("Empty dependency (no request)"));
}
return callback(null, createdModule); createdModule = new NormalModule(createData);
}
createdModule = this.hooks.module.call(
createdModule,
createData,
resolveData
);
return callback(null, createdModule);
}
);
}); });
}); });
} }

View File

@ -230,7 +230,7 @@ class ProgressPlugin {
); );
} }
const percentage = 0.1 + percentageFactor * 0.6; const percentage = 0.1 + percentageFactor * 0.55;
if (showEntries) { if (showEntries) {
items.push(`${doneEntries}/${entriesCount} entries`); items.push(`${doneEntries}/${entriesCount} entries`);
@ -440,7 +440,7 @@ class ProgressPlugin {
handler(0.1, "building"); handler(0.1, "building");
}, },
done() { done() {
handler(0.7, "building"); handler(0.65, "building");
} }
}); });
const interceptHook = (hook, progress, name) => { const interceptHook = (hook, progress, name) => {
@ -484,6 +484,7 @@ class ProgressPlugin {
interceptHook(compiler.hooks.compile, 0.04, "compile"); interceptHook(compiler.hooks.compile, 0.04, "compile");
interceptHook(compiler.hooks.thisCompilation, 0.05, "setup compilation"); interceptHook(compiler.hooks.thisCompilation, 0.05, "setup compilation");
interceptHook(compiler.hooks.compilation, 0.06, "setup compilation"); interceptHook(compiler.hooks.compilation, 0.06, "setup compilation");
interceptHook(compiler.hooks.finishMake, 0.69, "finish building");
interceptHook(compiler.hooks.emit, 0.95, "emitting"); interceptHook(compiler.hooks.emit, 0.95, "emitting");
interceptHook(compiler.hooks.afterEmit, 0.98, "after emitting"); interceptHook(compiler.hooks.afterEmit, 0.98, "after emitting");
interceptHook(compiler.hooks.done, 0.99, "done"); interceptHook(compiler.hooks.done, 0.99, "done");

View File

@ -160,6 +160,14 @@ exports.uncaughtErrorHandler = "__webpack_require__.oe";
*/ */
exports.scriptNonce = "__webpack_require__.nc"; exports.scriptNonce = "__webpack_require__.nc";
/**
* function to load a script tag.
* Arguments: (url: string, done: (event) => void), key?: string | number) => void
* done function is called when loading has finished or timeout occurred.
* It will attach to existing script tags with data-webpack == key or src == url.
*/
exports.loadScript = "__webpack_require__.l";
/** /**
* the chunk name of the chunk with the runtime * the chunk name of the chunk with the runtime
*/ */

View File

@ -17,6 +17,7 @@ const GetChunkFilenameRuntimeModule = require("./runtime/GetChunkFilenameRuntime
const GetMainFilenameRuntimeModule = require("./runtime/GetMainFilenameRuntimeModule"); const GetMainFilenameRuntimeModule = require("./runtime/GetMainFilenameRuntimeModule");
const GlobalRuntimeModule = require("./runtime/GlobalRuntimeModule"); const GlobalRuntimeModule = require("./runtime/GlobalRuntimeModule");
const HasOwnPropertyRuntimeModule = require("./runtime/HasOwnPropertyRuntimeModule"); const HasOwnPropertyRuntimeModule = require("./runtime/HasOwnPropertyRuntimeModule");
const LoadScriptRuntimeModule = require("./runtime/LoadScriptRuntimeModule");
const MakeNamespaceObjectRuntimeModule = require("./runtime/MakeNamespaceObjectRuntimeModule"); const MakeNamespaceObjectRuntimeModule = require("./runtime/MakeNamespaceObjectRuntimeModule");
const PublicPathRuntimeModule = require("./runtime/PublicPathRuntimeModule"); const PublicPathRuntimeModule = require("./runtime/PublicPathRuntimeModule");
const SystemContextRuntimeModule = require("./runtime/SystemContextRuntimeModule"); const SystemContextRuntimeModule = require("./runtime/SystemContextRuntimeModule");
@ -46,7 +47,8 @@ const GLOBALS_ON_REQUIRE = [
RuntimeGlobals.wasmInstances, RuntimeGlobals.wasmInstances,
RuntimeGlobals.instantiateWasm, RuntimeGlobals.instantiateWasm,
RuntimeGlobals.shareScopeMap, RuntimeGlobals.shareScopeMap,
RuntimeGlobals.initializeSharing RuntimeGlobals.initializeSharing,
RuntimeGlobals.loadScript
]; ];
const MODULE_DEPENDENCIES = { const MODULE_DEPENDENCIES = {
@ -265,6 +267,12 @@ class RuntimePlugin {
compilation.addRuntimeModule(chunk, new ShareRuntimeModule()); compilation.addRuntimeModule(chunk, new ShareRuntimeModule());
return true; return true;
}); });
compilation.hooks.runtimeRequirementInTree
.for(RuntimeGlobals.loadScript)
.tap("RuntimePlugin", (chunk, set) => {
compilation.addRuntimeModule(chunk, new LoadScriptRuntimeModule());
return true;
});
// TODO webpack 6: remove CompatRuntimePlugin // TODO webpack 6: remove CompatRuntimePlugin
compilation.hooks.additionalTreeRuntimeRequirements.tap( compilation.hooks.additionalTreeRuntimeRequirements.tap(
"RuntimePlugin", "RuntimePlugin",

View File

@ -19,7 +19,7 @@ class Stats {
} }
/** /**
* @returns {boolean} true if the compilation encountered an error * @returns {boolean} true if the compilation had a warning
*/ */
hasWarnings() { hasWarnings() {
return ( return (
@ -29,7 +29,7 @@ class Stats {
} }
/** /**
* @returns {boolean} true if the compilation had a warning * @returns {boolean} true if the compilation encountered an error
*/ */
hasErrors() { hasErrors() {
return ( return (

View File

@ -82,11 +82,14 @@ const applyWebpackOptionsDefaults = options => {
const development = mode === "development"; const development = mode === "development";
const production = mode === "production" || !mode; const production = mode === "production" || !mode;
if ( if (typeof options.entry !== "function") {
typeof options.entry !== "function" && for (const key of Object.keys(options.entry)) {
Object.keys(options.entry).length === 0 F(
) { options.entry[key],
options.entry.main = { import: ["./src"] }; "import",
() => /** @type {[string]} */ (["./src"])
);
}
} }
F(options, "devtool", () => (development ? "eval" : false)); F(options, "devtool", () => (development ? "eval" : false));

View File

@ -129,13 +129,15 @@ const getNormalizedWebpackOptions = config => {
...devServer ...devServer
})), })),
devtool: config.devtool, devtool: config.devtool,
entry: nestedConfig(config.entry, entry => { entry:
if (typeof entry === "function") { config.entry === undefined
return () => ? { main: {} }
Promise.resolve().then(entry).then(getNormalizedEntryStatic); : typeof config.entry === "function"
} ? (fn => () =>
return getNormalizedEntryStatic(entry); Promise.resolve().then(fn).then(getNormalizedEntryStatic))(
}), config.entry
)
: getNormalizedEntryStatic(config.entry),
experiments: nestedConfig(config.experiments, experiments => ({ experiments: nestedConfig(config.experiments, experiments => ({
...experiments ...experiments
})), })),

View File

@ -32,13 +32,14 @@ class ModuleFederationPlugin {
*/ */
apply(compiler) { apply(compiler) {
const { _options: options } = this; const { _options: options } = this;
const library = options.library || { type: "var", name: options.name };
const remoteType =
options.remoteType || (options.library ? options.library.type : "script");
if ( if (
options.library && library &&
!compiler.options.output.enabledLibraryTypes.includes( !compiler.options.output.enabledLibraryTypes.includes(library.type)
options.library.type
)
) { ) {
compiler.options.output.enabledLibraryTypes.push(options.library.type); compiler.options.output.enabledLibraryTypes.push(library.type);
} }
compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => {
if ( if (
@ -49,7 +50,7 @@ class ModuleFederationPlugin {
) { ) {
new ContainerPlugin({ new ContainerPlugin({
name: options.name, name: options.name,
library: options.library || compiler.options.output.library, library,
filename: options.filename, filename: options.filename,
exposes: options.exposes exposes: options.exposes
}).apply(compiler); }).apply(compiler);
@ -61,10 +62,7 @@ class ModuleFederationPlugin {
: Object.keys(options.remotes).length > 0) : Object.keys(options.remotes).length > 0)
) { ) {
new ContainerReferencePlugin({ new ContainerReferencePlugin({
remoteType: remoteType,
options.remoteType ||
(options.library && options.library.type) ||
compiler.options.externalsType,
remotes: options.remotes remotes: options.remotes
}).apply(compiler); }).apply(compiler);
} }

View File

@ -148,7 +148,7 @@ const createTrace = (fs, outputPath) => {
profiler, profiler,
end: callback => { end: callback => {
// Wait until the write stream finishes. // Wait until the write stream finishes.
fsStream.on("finish", () => { fsStream.on("close", () => {
callback(); callback();
}); });
// Tear down the readable trace stream. // Tear down the readable trace stream.

View File

@ -10,6 +10,7 @@ const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -30,7 +31,7 @@ class CommonJsFullRequireDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
if (this.call) { if (this.call) {

View File

@ -14,6 +14,7 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -39,7 +40,7 @@ class CommonJsSelfReferenceDependency extends NullDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return [this.names]; return [this.names];

View File

@ -5,12 +5,17 @@
"use strict"; "use strict";
const Dependency = require("../Dependency");
const makeSerializable = require("../util/makeSerializable"); const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
class ContextElementDependency extends ModuleDependency { class ContextElementDependency extends ModuleDependency {
constructor(request, userRequest) { constructor(request, userRequest, referencedExports) {
super(request); super(request);
this.referencedExports = referencedExports;
if (userRequest) { if (userRequest) {
this.userRequest = userRequest; this.userRequest = userRequest;
@ -20,6 +25,30 @@ class ContextElementDependency extends ModuleDependency {
get type() { get type() {
return "context element"; return "context element";
} }
/**
* Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
getReferencedExports(moduleGraph) {
return this.referencedExports
? this.referencedExports.map(e => ({
name: e,
canMangle: false
}))
: Dependency.EXPORTS_OBJECT_REFERENCED;
}
serialize(context) {
context.write(this.referencedExports);
super.serialize(context);
}
deserialize(context) {
this.referencedExports = context.read();
super.deserialize(context);
}
} }
makeSerializable( makeSerializable(

View File

@ -5,6 +5,7 @@
"use strict"; "use strict";
const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
class DelegatedSourceDependency extends ModuleDependency { class DelegatedSourceDependency extends ModuleDependency {
@ -17,4 +18,9 @@ class DelegatedSourceDependency extends ModuleDependency {
} }
} }
makeSerializable(
DelegatedSourceDependency,
"webpack/lib/dependencies/DelegatedSourceDependency"
);
module.exports = DelegatedSourceDependency; module.exports = DelegatedSourceDependency;

View File

@ -5,6 +5,7 @@
"use strict"; "use strict";
const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
class EntryDependency extends ModuleDependency { class EntryDependency extends ModuleDependency {
@ -20,4 +21,6 @@ class EntryDependency extends ModuleDependency {
} }
} }
makeSerializable(EntryDependency, "webpack/lib/dependencies/EntryDependency");
module.exports = EntryDependency; module.exports = EntryDependency;

View File

@ -19,6 +19,7 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../Module")} Module */ /** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -395,7 +396,7 @@ class HarmonyExportImportedSpecifierDependency extends HarmonyImportDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
const mode = this.getMode(moduleGraph, false); const mode = this.getMode(moduleGraph, false);

View File

@ -16,6 +16,7 @@ const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../Module")} Module */ /** @typedef {import("../Module")} Module */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -42,7 +43,7 @@ class HarmonyImportDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return Dependency.NO_EXPORTS_REFERENCED; return Dependency.NO_EXPORTS_REFERENCED;

View File

@ -14,6 +14,7 @@ const HarmonyImportDependency = require("./HarmonyImportDependency");
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */ /** @typedef {import("../Dependency").ExportsSpec} ExportsSpec */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../WebpackError")} WebpackError */
@ -99,7 +100,7 @@ class HarmonyImportSpecifierDependency extends HarmonyImportDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
let ids = this.getIds(moduleGraph); let ids = this.getIds(moduleGraph);

View File

@ -1,45 +0,0 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const makeSerializable = require("../util/makeSerializable");
/** @typedef {import("../ChunkGroup").ChunkGroupOptions} ChunkGroupOptions */
/** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
class ImportDependenciesBlock extends AsyncDependenciesBlock {
/**
* @param {ChunkGroupOptions} groupOptions options for the chunk group
* @param {DependencyLocation} loc location info
* @param {string} request request string for the block
* @param {[number, number]} range position of the block
*/
constructor(groupOptions, loc, request, range) {
super(groupOptions, loc, request);
/** @type {[number, number]} */
this.range = range;
}
serialize(context) {
const { write } = context;
write(this.range);
super.serialize(context);
}
deserialize(context) {
const { read } = context;
this.range = read();
super.deserialize(context);
}
}
makeSerializable(
ImportDependenciesBlock,
"webpack/lib/dependencies/ImportDependenciesBlock"
);
module.exports = ImportDependenciesBlock;

View File

@ -5,17 +5,27 @@
"use strict"; "use strict";
const Dependency = require("../Dependency");
const makeSerializable = require("../util/makeSerializable"); const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../dependencies/ImportDependenciesBlock")} ImportDependenciesBlock */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
class ImportDependency extends ModuleDependency { class ImportDependency extends ModuleDependency {
constructor(request) { /**
* @param {string} request the request
* @param {[number, number]} range expression range
* @param {string[][]=} referencedExports list of referenced exports
*/
constructor(request, range, referencedExports) {
super(request); super(request);
this.range = range;
this.referencedExports = referencedExports;
} }
get type() { get type() {
@ -25,6 +35,32 @@ class ImportDependency extends ModuleDependency {
get category() { get category() {
return "esm"; return "esm";
} }
/**
* Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph
* @returns {(string[] | ReferencedExport)[]} referenced exports
*/
getReferencedExports(moduleGraph) {
return this.referencedExports
? this.referencedExports.map(e => ({
name: e,
canMangle: false
}))
: Dependency.EXPORTS_OBJECT_REFERENCED;
}
serialize(context) {
context.write(this.range);
context.write(this.referencedExports);
super.serialize(context);
}
deserialize(context) {
this.range = context.read();
this.referencedExports = context.read();
super.deserialize(context);
}
} }
makeSerializable(ImportDependency, "webpack/lib/dependencies/ImportDependency"); makeSerializable(ImportDependency, "webpack/lib/dependencies/ImportDependency");
@ -42,7 +78,7 @@ ImportDependency.Template = class ImportDependencyTemplate extends ModuleDepende
{ runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements } { runtimeTemplate, module, moduleGraph, chunkGraph, runtimeRequirements }
) { ) {
const dep = /** @type {ImportDependency} */ (dependency); const dep = /** @type {ImportDependency} */ (dependency);
const block = /** @type {ImportDependenciesBlock} */ (moduleGraph.getParentBlock( const block = /** @type {AsyncDependenciesBlock} */ (moduleGraph.getParentBlock(
dep dep
)); ));
const content = runtimeTemplate.moduleNamespacePromise({ const content = runtimeTemplate.moduleNamespacePromise({
@ -55,7 +91,7 @@ ImportDependency.Template = class ImportDependencyTemplate extends ModuleDepende
runtimeRequirements runtimeRequirements
}); });
source.replace(block.range[0], block.range[1] - 1, content); source.replace(dep.range[0], dep.range[1] - 1, content);
} }
}; };

View File

@ -6,17 +6,22 @@
"use strict"; "use strict";
const makeSerializable = require("../util/makeSerializable"); const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ImportDependency = require("./ImportDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
class ImportEagerDependency extends ModuleDependency { class ImportEagerDependency extends ImportDependency {
constructor(request, range) { /**
super(request); * @param {string} request the request
* @param {[number, number]} range expression range
this.range = range; * @param {string[][]=} referencedExports list of referenced exports
*/
constructor(request, range, referencedExports) {
super(request, range, referencedExports);
} }
get type() { get type() {
@ -33,7 +38,7 @@ makeSerializable(
"webpack/lib/dependencies/ImportEagerDependency" "webpack/lib/dependencies/ImportEagerDependency"
); );
ImportEagerDependency.Template = class ImportEagerDependencyTemplate extends ModuleDependency.Template { ImportEagerDependency.Template = class ImportEagerDependencyTemplate extends ImportDependency.Template {
/** /**
* @param {Dependency} dependency the dependency for which the template should be applied * @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified * @param {ReplaceSource} source the current replace source which can be modified

View File

@ -5,11 +5,11 @@
"use strict"; "use strict";
const AsyncDependenciesBlock = require("../AsyncDependenciesBlock");
const CommentCompilationWarning = require("../CommentCompilationWarning"); const CommentCompilationWarning = require("../CommentCompilationWarning");
const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
const ContextDependencyHelpers = require("./ContextDependencyHelpers"); const ContextDependencyHelpers = require("./ContextDependencyHelpers");
const ImportContextDependency = require("./ImportContextDependency"); const ImportContextDependency = require("./ImportContextDependency");
const ImportDependenciesBlock = require("./ImportDependenciesBlock");
const ImportDependency = require("./ImportDependency"); const ImportDependency = require("./ImportDependency");
const ImportEagerDependency = require("./ImportEagerDependency"); const ImportEagerDependency = require("./ImportEagerDependency");
const ImportWeakDependency = require("./ImportWeakDependency"); const ImportWeakDependency = require("./ImportWeakDependency");
@ -31,6 +31,8 @@ class ImportParserPlugin {
let mode = "lazy"; let mode = "lazy";
let include = null; let include = null;
let exclude = null; let exclude = null;
/** @type {string[][] | null} */
let exports = null;
/** @type {RawChunkGroupOptions} */ /** @type {RawChunkGroupOptions} */
const groupOptions = {}; const groupOptions = {};
@ -149,6 +151,30 @@ class ImportParserPlugin {
exclude = new RegExp(importOptions.webpackExclude); exclude = new RegExp(importOptions.webpackExclude);
} }
} }
if (importOptions.webpackExports !== undefined) {
if (
!(
typeof importOptions.webpackExports === "string" ||
(Array.isArray(importOptions.webpackExports) &&
importOptions.webpackExports.every(
item => typeof item === "string"
))
)
) {
parser.state.module.addWarning(
new UnsupportedFeatureWarning(
`\`webpackExports\` expected a string or an array of strings, but received: ${importOptions.webpackExports}.`,
expr.loc
)
);
} else {
if (typeof importOptions.webpackExports === "string") {
exports = [[importOptions.webpackExports]];
} else {
exports = Array.from(importOptions.webpackExports, e => [e]);
}
}
}
} }
if (param.isString()) { if (param.isString()) {
@ -162,22 +188,29 @@ class ImportParserPlugin {
} }
if (mode === "eager") { if (mode === "eager") {
const dep = new ImportEagerDependency(param.string, expr.range); const dep = new ImportEagerDependency(
param.string,
expr.range,
exports
);
parser.state.current.addDependency(dep); parser.state.current.addDependency(dep);
} else if (mode === "weak") { } else if (mode === "weak") {
const dep = new ImportWeakDependency(param.string, expr.range); const dep = new ImportWeakDependency(
param.string,
expr.range,
exports
);
parser.state.current.addDependency(dep); parser.state.current.addDependency(dep);
} else { } else {
const depBlock = new ImportDependenciesBlock( const depBlock = new AsyncDependenciesBlock(
{ {
...groupOptions, ...groupOptions,
name: chunkName name: chunkName
}, },
expr.loc, expr.loc,
param.string, param.string
expr.range
); );
const dep = new ImportDependency(param.string); const dep = new ImportDependency(param.string, expr.range, exports);
dep.loc = expr.loc; dep.loc = expr.loc;
depBlock.addDependency(dep); depBlock.addDependency(dep);
parser.state.current.addBlock(depBlock); parser.state.current.addBlock(depBlock);
@ -216,7 +249,8 @@ class ImportParserPlugin {
mode, mode,
namespaceObject: parser.state.module.buildMeta.strictHarmonyModule namespaceObject: parser.state.module.buildMeta.strictHarmonyModule
? "strict" ? "strict"
: true : true,
referencedExports: exports
}, },
parser parser
); );

View File

@ -6,17 +6,22 @@
"use strict"; "use strict";
const makeSerializable = require("../util/makeSerializable"); const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ImportDependency = require("./ImportDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */
class ImportWeakDependency extends ModuleDependency { class ImportWeakDependency extends ImportDependency {
constructor(request, range) { /**
super(request); * @param {string} request the request
* @param {[number, number]} range expression range
this.range = range; * @param {string[][]=} referencedExports list of referenced exports
*/
constructor(request, range, referencedExports) {
super(request, range, referencedExports);
this.weak = true; this.weak = true;
} }
@ -30,7 +35,7 @@ makeSerializable(
"webpack/lib/dependencies/ImportWeakDependency" "webpack/lib/dependencies/ImportWeakDependency"
); );
ImportWeakDependency.Template = class ImportDependencyTemplate extends ModuleDependency.Template { ImportWeakDependency.Template = class ImportDependencyTemplate extends ImportDependency.Template {
/** /**
* @param {Dependency} dependency the dependency for which the template should be applied * @param {Dependency} dependency the dependency for which the template should be applied
* @param {ReplaceSource} source the current replace source which can be modified * @param {ReplaceSource} source the current replace source which can be modified

View File

@ -14,6 +14,7 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ /** @typedef {import("../DependencyTemplates")} DependencyTemplates */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -47,7 +48,7 @@ class ModuleDecoratorDependency extends NullDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return this.allowExportsAccess return this.allowExportsAccess

View File

@ -12,6 +12,7 @@ const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
@ -25,7 +26,7 @@ class RequireIncludeDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
// This doesn't use any export // This doesn't use any export

View File

@ -10,6 +10,7 @@ const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
const ModuleDependencyAsId = require("./ModuleDependencyTemplateAsId"); const ModuleDependencyAsId = require("./ModuleDependencyTemplateAsId");
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
class RequireResolveDependency extends ModuleDependency { class RequireResolveDependency extends ModuleDependency {
@ -30,7 +31,7 @@ class RequireResolveDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
// This doesn't use any export // This doesn't use any export

View File

@ -8,6 +8,7 @@
const makeSerializable = require("../util/makeSerializable"); const makeSerializable = require("../util/makeSerializable");
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
class WebAssemblyExportImportedDependency extends ModuleDependency { class WebAssemblyExportImportedDependency extends ModuleDependency {
@ -24,7 +25,7 @@ class WebAssemblyExportImportedDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return [[this.name]]; return [[this.name]];

View File

@ -10,6 +10,7 @@ const UnsupportedWebAssemblyFeatureError = require("../wasm/UnsupportedWebAssemb
const ModuleDependency = require("./ModuleDependency"); const ModuleDependency = require("./ModuleDependency");
/** @typedef {import("@webassemblyjs/ast").ModuleImportDescription} ModuleImportDescription */ /** @typedef {import("@webassemblyjs/ast").ModuleImportDescription} ModuleImportDescription */
/** @typedef {import("../Dependency").ReferencedExport} ReferencedExport */
/** @typedef {import("../ModuleGraph")} ModuleGraph */ /** @typedef {import("../ModuleGraph")} ModuleGraph */
/** @typedef {import("../WebpackError")} WebpackError */ /** @typedef {import("../WebpackError")} WebpackError */
@ -33,7 +34,7 @@ class WebAssemblyImportDependency extends ModuleDependency {
/** /**
* Returns list of exports referenced by this dependency * Returns list of exports referenced by this dependency
* @param {ModuleGraph} moduleGraph module graph * @param {ModuleGraph} moduleGraph module graph
* @returns {string[][]} referenced exports * @returns {(string[] | ReferencedExport)[]} referenced exports
*/ */
getReferencedExports(moduleGraph) { getReferencedExports(moduleGraph) {
return [[this.name]]; return [[this.name]];

View File

@ -32,7 +32,7 @@ const makeSerializable = require("../util/makeSerializable");
const propertyAccess = require("../util/propertyAccess"); const propertyAccess = require("../util/propertyAccess");
/** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").WebpackOptions} WebpackOptions */ /** @typedef {import("../../declarations/WebpackOptions").WebpackOptionsNormalized} WebpackOptions */
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation")} Compilation */ /** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */

View File

@ -485,7 +485,9 @@ class ModuleConcatenationPlugin {
const importedNames = compilation.getDependencyReferencedExports(dep); const importedNames = compilation.getDependencyReferencedExports(dep);
if ( if (
importedNames.every(i => i.length > 0) || importedNames.every(i =>
Array.isArray(i) ? i.length > 0 : i.name.length > 0
) ||
Array.isArray(moduleGraph.getProvidedExports(module)) Array.isArray(moduleGraph.getProvidedExports(module))
) { ) {
set.add(connection.module); set.add(connection.module);

View File

@ -0,0 +1,143 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
const { SyncWaterfallHook } = require("tapable");
const Compilation = require("../Compilation");
const RuntimeGlobals = require("../RuntimeGlobals");
const Template = require("../Template");
const HelperRuntimeModule = require("./HelperRuntimeModule");
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/**
* @typedef {Object} LoadScriptCompilationHooks
* @property {SyncWaterfallHook<[string, Chunk]>} createScript
*/
/** @type {WeakMap<Compilation, LoadScriptCompilationHooks>} */
const compilationHooksMap = new WeakMap();
class LoadScriptRuntimeModule extends HelperRuntimeModule {
/**
* @param {Compilation} compilation the compilation
* @returns {LoadScriptCompilationHooks} hooks
*/
static getCompilationHooks(compilation) {
if (!(compilation instanceof Compilation)) {
throw new TypeError(
"The 'compilation' argument must be an instance of Compilation"
);
}
let hooks = compilationHooksMap.get(compilation);
if (hooks === undefined) {
hooks = {
createScript: new SyncWaterfallHook(["source", "chunk"])
};
compilationHooksMap.set(compilation, hooks);
}
return hooks;
}
constructor() {
super("load script");
}
/**
* @returns {string} runtime code
*/
generate() {
const { compilation } = this;
const { runtimeTemplate, outputOptions } = compilation;
const {
jsonpScriptType: scriptType,
chunkLoadTimeout: loadTimeout,
crossOriginLoading
} = outputOptions;
const fn = RuntimeGlobals.loadScript;
const { createScript } = LoadScriptRuntimeModule.getCompilationHooks(
compilation
);
const code = Template.asString([
"script = document.createElement('script');",
scriptType ? `script.type = ${JSON.stringify(scriptType)};` : "",
"script.charset = 'utf-8';",
`script.timeout = ${loadTimeout / 1000};`,
`if (${RuntimeGlobals.scriptNonce}) {`,
Template.indent(
`script.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
),
"}",
'script.setAttribute("data-webpack", key);',
`script.src = url;`,
crossOriginLoading
? Template.asString([
"if (script.src.indexOf(window.location.origin + '/') !== 0) {",
Template.indent(
`script.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
),
"}"
])
: ""
]);
return Template.asString([
"var inProgress = {};",
"// loadScript function to load a script via script tag",
`${fn} = ${runtimeTemplate.basicFunction("url, done, key", [
"if(inProgress[url]) { inProgress[url].push(done); return; }",
"var script, needAttach;",
"if(key !== undefined) {",
Template.indent([
'var scripts = document.getElementsByTagName("script");',
"for(var i = 0; i < scripts.length; i++) {",
Template.indent([
"var s = scripts[i];",
'if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == key) { script = s; break; }'
]),
"}"
]),
"}",
"if(!script) {",
Template.indent([
"needAttach = true;",
createScript.call(code, this.chunk)
]),
"}",
"inProgress[url] = [done];",
"var onScriptComplete = " +
runtimeTemplate.basicFunction(
"event",
Template.asString([
`onScriptComplete = ${runtimeTemplate.basicFunction("", "")}`,
"// avoid mem leaks in IE.",
"script.onerror = script.onload = null;",
"clearTimeout(timeout);",
"var doneFns = inProgress[url];",
"delete inProgress[url];",
"script.parentNode.removeChild(script);",
`doneFns && doneFns.forEach(${runtimeTemplate.returningFunction(
"fn(event)",
"fn"
)});`
])
),
";",
`var timeout = setTimeout(${runtimeTemplate.basicFunction(
"",
"onScriptComplete({ type: 'timeout', target: script })"
)}, ${loadTimeout});`,
"script.onerror = script.onload = onScriptComplete;",
"needAttach && document.head.appendChild(script);"
])};`
]);
}
}
module.exports = LoadScriptRuntimeModule;

View File

@ -0,0 +1,16 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
*/
"use strict";
class DateObjectSerializer {
serialize(obj, { write }) {
write(obj.getTime());
}
deserialize({ read }) {
return new Date(read());
}
}
module.exports = DateObjectSerializer;

View File

@ -5,6 +5,7 @@
"use strict"; "use strict";
const ArraySerializer = require("./ArraySerializer"); const ArraySerializer = require("./ArraySerializer");
const DateObjectSerializer = require("./DateObjectSerializer");
const ErrorObjectSerializer = require("./ErrorObjectSerializer"); const ErrorObjectSerializer = require("./ErrorObjectSerializer");
const MapObjectSerializer = require("./MapObjectSerializer"); const MapObjectSerializer = require("./MapObjectSerializer");
const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer"); const NullPrototypeObjectSerializer = require("./NullPrototypeObjectSerializer");
@ -94,6 +95,7 @@ jsTypes.set(Array, new ArraySerializer());
jsTypes.set(null, new NullPrototypeObjectSerializer()); jsTypes.set(null, new NullPrototypeObjectSerializer());
jsTypes.set(Map, new MapObjectSerializer()); jsTypes.set(Map, new MapObjectSerializer());
jsTypes.set(Set, new SetObjectSerializer()); jsTypes.set(Set, new SetObjectSerializer());
jsTypes.set(Date, new DateObjectSerializer());
jsTypes.set(RegExp, new RegExpObjectSerializer()); jsTypes.set(RegExp, new RegExpObjectSerializer());
jsTypes.set(Error, new ErrorObjectSerializer(Error)); jsTypes.set(Error, new ErrorObjectSerializer(Error));
jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError)); jsTypes.set(EvalError, new ErrorObjectSerializer(EvalError));
@ -267,17 +269,26 @@ class ObjectMiddleware extends SerializerMiddleware {
} catch (e) { } catch (e) {
// ignore -> fallback // ignore -> fallback
} }
if (typeof item === "object" && item !== null && item.constructor) { if (typeof item === "object" && item !== null) {
if (item.constructor === Object) if (item.constructor) {
return `Object { ${Object.keys(item).join(", ")} }`; if (item.constructor === Object)
if (item.constructor === Map) return `Map { ${item.size} items }`; return `Object { ${Object.keys(item).join(", ")} }`;
if (item.constructor === Array) if (item.constructor === Map) return `Map { ${item.size} items }`;
return `Array { ${item.length} items }`; if (item.constructor === Array)
if (item.constructor === Set) return `Set { ${item.size} items }`; return `Array { ${item.length} items }`;
if (item.constructor === RegExp) return item.toString(); if (item.constructor === Set) return `Set { ${item.size} items }`;
return `${item.constructor.name}`; if (item.constructor === RegExp) return item.toString();
return `${item.constructor.name}`;
}
return `Object [null prototype] { ${Object.keys(item).join(
", "
)} }`;
}
try {
return `${item}`;
} catch (e) {
return `(${e.message})`;
} }
return `${item}`;
}) })
.join(" -> "); .join(" -> ");
}; };

View File

@ -30,9 +30,11 @@ const { versionToString } = require("./utils");
/** /**
* @typedef {Object} ConsumeOptions * @typedef {Object} ConsumeOptions
* @property {string=} import fallback request * @property {string=} import fallback request
* @property {string=} importResolved resolved fallback request
* @property {string} shareKey global share key * @property {string} shareKey global share key
* @property {string} shareScope share scope * @property {string} shareScope share scope
* @property {(number|string)[]} requiredVersion version requirement * @property {(number|string)[] | undefined} requiredVersion version requirement
* @property {string} packageName package name to determine required version automatically
* @property {boolean} strictVersion don't use shared version even if version isn't valid * @property {boolean} strictVersion don't use shared version even if version isn't valid
* @property {boolean} singleton use single global version * @property {boolean} singleton use single global version
* @property {boolean} eager include the fallback module in a sync way * @property {boolean} eager include the fallback module in a sync way
@ -57,15 +59,15 @@ class ConsumeSharedModule extends Module {
const { const {
shareKey, shareKey,
shareScope, shareScope,
import: request, importResolved,
requiredVersion, requiredVersion,
strictVersion, strictVersion,
singleton, singleton,
eager eager
} = this.options; } = this.options;
return `consume-shared-module|${shareScope}|${shareKey}|${versionToString( return `consume-shared-module|${shareScope}|${shareKey}|${
requiredVersion requiredVersion && versionToString(requiredVersion)
)}|${strictVersion}|${request}|${singleton}|${eager}`; }|${strictVersion}|${importResolved}|${singleton}|${eager}`;
} }
/** /**
@ -76,16 +78,18 @@ class ConsumeSharedModule extends Module {
const { const {
shareKey, shareKey,
shareScope, shareScope,
import: request, importResolved,
requiredVersion, requiredVersion,
strictVersion, strictVersion,
singleton, singleton,
eager eager
} = this.options; } = this.options;
return `shared module (${shareScope}) ${shareKey}@${versionToString( return `consume shared module (${shareScope}) ${shareKey}@${versionToString(
requiredVersion requiredVersion
)}${strictVersion ? " (strict)" : ""}${singleton ? " (singleton)" : ""}${ )}${strictVersion ? " (strict)" : ""}${singleton ? " (singleton)" : ""}${
request ? ` -> ${request}` : "" importResolved
? ` (fallback: ${requestShortener.shorten(importResolved)})`
: ""
}${eager ? " (eager)" : ""}`; }${eager ? " (eager)" : ""}`;
} }

View File

@ -9,13 +9,20 @@ const validateOptions = require("schema-utils");
const schema = require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json"); const schema = require("../../schemas/plugins/sharing/ConsumeSharedPlugin.json");
const ModuleNotFoundError = require("../ModuleNotFoundError"); const ModuleNotFoundError = require("../ModuleNotFoundError");
const RuntimeGlobals = require("../RuntimeGlobals"); const RuntimeGlobals = require("../RuntimeGlobals");
const WebpackError = require("../WebpackError");
const { parseOptions } = require("../container/options"); const { parseOptions } = require("../container/options");
const LazySet = require("../util/LazySet"); const LazySet = require("../util/LazySet");
const ConsumeFallbackDependency = require("./ConsumeFallbackDependency"); const ConsumeFallbackDependency = require("./ConsumeFallbackDependency");
const ConsumeSharedModule = require("./ConsumeSharedModule"); const ConsumeSharedModule = require("./ConsumeSharedModule");
const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule"); const ConsumeSharedRuntimeModule = require("./ConsumeSharedRuntimeModule");
const ProvidedDependency = require("./ProvidedDependency"); const ProvidedDependency = require("./ProvidedDependency");
const { parseRequiredVersion, isRequiredVersion } = require("./utils"); const { resolveMatchedConfigs } = require("./resolveMatchedConfigs");
const {
parseRequiredVersion,
isRequiredVersion,
getDescriptionFile,
getRequiredVersionFromDescriptionFile
} = require("./utils");
/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */ /** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
/** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */ /** @typedef {import("../../declarations/plugins/sharing/ConsumeSharedPlugin").ConsumeSharedPluginOptions} ConsumeSharedPluginOptions */
@ -37,12 +44,12 @@ class ConsumeSharedPlugin {
validateOptions(schema, options, { name: "Consumes Shared Plugin" }); validateOptions(schema, options, { name: "Consumes Shared Plugin" });
} }
/** @type {[string, ConsumeOptions][]} */ /** @type {[string, ConsumesConfig][]} */
this._consumes = parseOptions( this._consumes = parseOptions(
options.consumes, options.consumes,
(item, key) => { (item, key) => {
if (Array.isArray(item)) throw new Error("Unexpected array in options"); if (Array.isArray(item)) throw new Error("Unexpected array in options");
/** @type {ConsumeOptions} */ /** @type {ConsumesConfig} */
let result = let result =
item === key || !isRequiredVersion(item) item === key || !isRequiredVersion(item)
? // item is a request/key ? // item is a request/key
@ -51,6 +58,7 @@ class ConsumeSharedPlugin {
shareScope: options.shareScope || "default", shareScope: options.shareScope || "default",
shareKey: key, shareKey: key,
requiredVersion: undefined, requiredVersion: undefined,
packageName: undefined,
strictVersion: false, strictVersion: false,
singleton: false, singleton: false,
eager: false eager: false
@ -61,8 +69,10 @@ class ConsumeSharedPlugin {
import: key, import: key,
shareScope: options.shareScope || "default", shareScope: options.shareScope || "default",
shareKey: key, shareKey: key,
requiredVersion: parseRequiredVersion(item), requiredVersion:
typeof item === "string" ? parseRequiredVersion(item) : item,
strictVersion: true, strictVersion: true,
packageName: undefined,
singleton: false, singleton: false,
eager: false eager: false
}; };
@ -77,10 +87,10 @@ class ConsumeSharedPlugin {
? parseRequiredVersion(item.requiredVersion) ? parseRequiredVersion(item.requiredVersion)
: item.requiredVersion, : item.requiredVersion,
strictVersion: strictVersion:
item.requiredVersion && typeof item.strictVersion === "boolean"
(typeof item.strictVersion === "boolean"
? item.strictVersion ? item.strictVersion
: item.import !== false && !item.singleton), : item.import !== false && !item.singleton,
packageName: item.packageName,
singleton: !!item.singleton, singleton: !!item.singleton,
eager: !!item.eager eager: !!item.eager
}) })
@ -101,74 +111,146 @@ class ConsumeSharedPlugin {
normalModuleFactory normalModuleFactory
); );
/** @type {Map<string, ConsumeOptions>} */ let unresolvedConsumes, resolvedConsumes, prefixedConsumes;
const resolvedConsumes = new Map(); const promise = resolveMatchedConfigs(compilation, this._consumes).then(
/** @type {Map<string, ConsumeOptions>} */ ({ resolved, unresolved, prefixed }) => {
const unresolvedConsumes = new Map(); resolvedConsumes = resolved;
/** @type {Map<string, ConsumeOptions>} */ unresolvedConsumes = unresolved;
const prefixConsumes = new Map(); prefixedConsumes = prefixed;
const resolveContext = { }
/** @type {LazySet<string>} */ );
fileDependencies: new LazySet(),
/** @type {LazySet<string>} */
contextDependencies: new LazySet(),
/** @type {LazySet<string>} */
missingDependencies: new LazySet()
};
const resolver = compilation.resolverFactory.get( const resolver = compilation.resolverFactory.get(
"normal", "normal",
RESOLVE_OPTIONS RESOLVE_OPTIONS
); );
/** /**
* @param {string} request imported request * @param {string} context issuer directory
* @param {ConsumeOptions} options options * @param {string} request request
* @returns {Promise<void>} promise * @param {ConsumeOptions} config options
* @returns {Promise<ConsumeSharedModule>} create module
*/ */
const resolveConsume = (request, options) => { const createConsumeSharedModule = (context, request, config) => {
if (/^\.\.?(\/|$)/.test(request)) { const requiredVersionWarning = details => {
// relative request const error = new WebpackError(
return new Promise(resolve => { `No required version specified and unable to automatically determine one. ${details}`
);
error.file = `shared module ${request}`;
compilation.warnings.push(error);
};
const directFallback =
config.import &&
/^(\.\.?(\/|$)|\/|[A-Za-z]:|\\\\)/.test(config.import);
return Promise.all([
new Promise(resolve => {
if (!config.import) return resolve();
const resolveContext = {
/** @type {LazySet<string>} */
fileDependencies: new LazySet(),
/** @type {LazySet<string>} */
contextDependencies: new LazySet(),
/** @type {LazySet<string>} */
missingDependencies: new LazySet()
};
resolver.resolve( resolver.resolve(
{}, {},
compiler.context, directFallback ? compiler.context : context,
request, config.import,
resolveContext, resolveContext,
(err, result) => { (err, result) => {
compilation.contextDependencies.addAll(
resolveContext.contextDependencies
);
compilation.fileDependencies.addAll(
resolveContext.fileDependencies
);
compilation.missingDependencies.addAll(
resolveContext.missingDependencies
);
if (err) { if (err) {
compilation.errors.push( compilation.errors.push(
new ModuleNotFoundError(null, err, { new ModuleNotFoundError(null, err, {
name: `consumed shared module ${request}` name: `resolving fallback for shared module ${request}`
}) })
); );
return resolve(); return resolve();
} }
resolvedConsumes.set(result, options); resolve(result);
resolve();
} }
); );
}); }),
} else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) { new Promise(resolve => {
// absolute path if (config.requiredVersion !== undefined)
resolvedConsumes.set(request, options); return resolve(config.requiredVersion);
} else if (request.endsWith("/")) { let packageName = config.packageName;
// module request prefix if (packageName === undefined) {
prefixConsumes.set(request, options); if (/^(\/|[A-Za-z]:|\\\\)/.test(request)) {
} else { // For relative or absolute requests we don't automatically use a packageName.
// module request // If wished one can specify one with the packageName option.
unresolvedConsumes.set(request, options); return resolve();
} }
const match = /^((?:@[^\\/]+[\\/])?[^\\/]+)/.exec(request);
if (!match) {
requiredVersionWarning(
"Unable to extract the package name from request."
);
return resolve();
}
packageName = match[0];
}
getDescriptionFile(
compilation.inputFileSystem,
context,
["package.json"],
(err, result) => {
if (err) {
requiredVersionWarning(
`Unable to read description file: ${err}`
);
return resolve();
}
const { data, path: descriptionPath } = result;
if (!data) {
requiredVersionWarning(
`Unable to find description file in ${context}.`
);
return resolve();
}
const requiredVersion = getRequiredVersionFromDescriptionFile(
data,
packageName
);
if (typeof requiredVersion !== "string") {
requiredVersionWarning(
`Unable to find required version for "${packageName}" in description file (${descriptionPath}). It need to be in dependencies, devDependencies or peerDependencies.`
);
return resolve();
}
if (!isRequiredVersion(requiredVersion)) {
requiredVersionWarning(
`Required version in description file ("${requiredVersion}") is unsupported (too complex, weird syntax).`
);
return resolve();
}
resolve(parseRequiredVersion(requiredVersion));
}
);
})
]).then(([importResolved, requiredVersion]) => {
return new ConsumeSharedModule(
directFallback ? compiler.context : context,
{
...config,
importResolved,
import: importResolved ? config.import : undefined,
requiredVersion
}
);
});
}; };
const promise = Promise.all(
this._consumes.map(([name, config]) => resolveConsume(name, config))
).then(() => {
compilation.contextDependencies.addAll(
resolveContext.contextDependencies
);
compilation.fileDependencies.addAll(resolveContext.fileDependencies);
compilation.missingDependencies.addAll(
resolveContext.missingDependencies
);
});
normalModuleFactory.hooks.factorize.tapPromise( normalModuleFactory.hooks.factorize.tapPromise(
PLUGIN_NAME, PLUGIN_NAME,
({ context, request, dependencies }) => ({ context, request, dependencies }) =>
@ -182,12 +264,12 @@ class ConsumeSharedPlugin {
} }
const match = unresolvedConsumes.get(request); const match = unresolvedConsumes.get(request);
if (match !== undefined) { if (match !== undefined) {
return new ConsumeSharedModule(compiler.context, match); return createConsumeSharedModule(context, request, match);
} }
for (const [prefix, options] of prefixConsumes) { for (const [prefix, options] of prefixedConsumes) {
if (request.startsWith(prefix)) { if (request.startsWith(prefix)) {
const remainder = request.slice(prefix.length); const remainder = request.slice(prefix.length);
return new ConsumeSharedModule(compiler.context, { return createConsumeSharedModule(context, request, {
...options, ...options,
import: options.import import: options.import
? options.import + remainder ? options.import + remainder
@ -198,19 +280,20 @@ class ConsumeSharedPlugin {
} }
}) })
); );
normalModuleFactory.hooks.module.tap( normalModuleFactory.hooks.createModule.tapPromise(
PLUGIN_NAME, PLUGIN_NAME,
(module, createData, { context, request, dependencies }) => { ({ resource }, { context, dependencies }) => {
if ( if (
dependencies[0] instanceof ConsumeFallbackDependency || dependencies[0] instanceof ConsumeFallbackDependency ||
dependencies[0] instanceof ProvidedDependency dependencies[0] instanceof ProvidedDependency
) { ) {
return; return Promise.resolve();
} }
const options = resolvedConsumes.get(createData.resource); const options = resolvedConsumes.get(resource);
if (options !== undefined) { if (options !== undefined) {
return new ConsumeSharedModule(compiler.context, options); return createConsumeSharedModule(context, resource, options);
} }
return Promise.resolve();
} }
); );
compilation.hooks.additionalTreeRuntimeRequirements.tap( compilation.hooks.additionalTreeRuntimeRequirements.tap(

View File

@ -59,9 +59,9 @@ class ProvideModule extends Module {
* @returns {string} a user readable identifier of the module * @returns {string} a user readable identifier of the module
*/ */
readableIdentifier(requestShortener) { readableIdentifier(requestShortener) {
return `provide module (${this._shareScope}) ${this._name}@${ return `provide shared module (${this._shareScope}) ${this._name}@${
this._version && this._version.join(".") this._version && this._version.join(".")
} = ${this._request}`; } = ${requestShortener.shorten(this._request)}`;
} }
/** /**
@ -108,15 +108,6 @@ class ProvideModule extends Module {
callback(); callback();
} }
/**
* @param {Chunk} chunk the chunk which condition should be checked
* @param {Compilation} compilation the compilation
* @returns {boolean} true, if the chunk is ok for the module
*/
chunkCondition(chunk, { chunkGraph }) {
return chunkGraph.getNumberOfEntryModules(chunk) > 0;
}
/** /**
* @param {string=} type the source type for which the size should be estimated * @param {string=} type the source type for which the size should be estimated
* @returns {number} the estimated size of the module (must be non-zero) * @returns {number} the estimated size of the module (must be non-zero)

View File

@ -9,26 +9,24 @@ const validateOptions = require("schema-utils");
const schema = require("../../schemas/plugins/sharing/ProvideSharedPlugin.json"); const schema = require("../../schemas/plugins/sharing/ProvideSharedPlugin.json");
const WebpackError = require("../WebpackError"); const WebpackError = require("../WebpackError");
const { parseOptions } = require("../container/options"); const { parseOptions } = require("../container/options");
const LazySet = require("../util/LazySet");
const ProvideDependency = require("./ProvideDependency"); const ProvideDependency = require("./ProvideDependency");
const ProvideModuleFactory = require("./ProvideModuleFactory"); const ProvideModuleFactory = require("./ProvideModuleFactory");
const ProvidedDependency = require("./ProvidedDependency"); const ProvidedDependency = require("./ProvidedDependency");
const { parseVersion } = require("./utils"); const { parseVersion } = require("./utils");
/** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */ /** @typedef {import("../../declarations/plugins/sharing/ProvideSharedPlugin").ProvideSharedPluginOptions} ProvideSharedPluginOptions */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../ResolverFactory").ResolveOptionsWithDependencyType} ResolveOptionsWithDependencyType */
/** /**
* @typedef {Object} ProvideOptions * @typedef {Object} ProvideOptions
* @property {string} import * @property {string} shareKey
* @property {string} shareScope * @property {string} shareScope
* @property {(string|number)[] | undefined | false} version * @property {(string|number)[] | undefined | false} version
* @property {boolean} eager * @property {boolean} eager
*/ */
/** @type {ResolveOptionsWithDependencyType} */ /** @typedef {Map<string, { config: ProvideOptions, version: (string|number)[] | undefined | false }>} ResolvedProvideMap */
const RESOLVE_OPTIONS = { dependencyType: "esm" };
class ProvideSharedPlugin { class ProvideSharedPlugin {
/** /**
@ -45,7 +43,7 @@ class ProvideSharedPlugin {
throw new Error("Unexpected array of provides"); throw new Error("Unexpected array of provides");
/** @type {ProvideOptions} */ /** @type {ProvideOptions} */
const result = { const result = {
import: item, shareKey: item,
version: undefined, version: undefined,
shareScope: options.shareScope || "default", shareScope: options.shareScope || "default",
eager: false eager: false
@ -53,7 +51,7 @@ class ProvideSharedPlugin {
return result; return result;
}, },
item => ({ item => ({
import: item.import, shareKey: item.shareKey,
version: version:
typeof item.version === "string" typeof item.version === "string"
? parseVersion(item.version) ? parseVersion(item.version)
@ -75,102 +73,132 @@ class ProvideSharedPlugin {
* @returns {void} * @returns {void}
*/ */
apply(compiler) { apply(compiler) {
const { _provides: provides } = this; /** @type {WeakMap<Compilation, ResolvedProvideMap>} */
const compilationData = new WeakMap();
for (const [name, config] of provides) { compiler.hooks.compilation.tap(
compiler.hooks.make.tapAsync( "ProvideSharedPlugin",
"ProvideSharedPlugin", (compilation, { normalModuleFactory }) => {
(compilation, callback) => { /** @type {ResolvedProvideMap} */
let version = config.version; const resolvedProvideMap = new Map();
const addModule = () => { /** @type {Map<string, ProvideOptions>} */
compilation.addInclude( const matchProvides = new Map();
compiler.context, /** @type {Map<string, ProvideOptions>} */
new ProvideDependency( const prefixMatchProvides = new Map();
config.shareScope, for (const [request, config] of this._provides) {
name, if (/^(\/|[A-Za-z]:\\|\\\\|\.\.?(\/|$))/.test(request)) {
version || false, // relative request
config.import, resolvedProvideMap.set(request, {
config.eager config,
), version: config.version
{ });
name: undefined } else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) {
}, // absolute path
err => callback(err) resolvedProvideMap.set(request, {
); config,
}; version: config.version
if ( });
version !== undefined || } else if (request.endsWith("/")) {
config.import.startsWith("./") || // module request prefix
config.import.startsWith("../") prefixMatchProvides.set(request, config);
) { } else {
return addModule(); // module request
matchProvides.set(request, config);
} }
const resolveContext = {
/** @type {LazySet<string>} */
fileDependencies: new LazySet(),
/** @type {LazySet<string>} */
contextDependencies: new LazySet(),
/** @type {LazySet<string>} */
missingDependencies: new LazySet()
};
const resolver = compiler.resolverFactory.get(
"normal",
RESOLVE_OPTIONS
);
resolver.resolve(
{},
compiler.context,
config.import,
resolveContext,
(err, result, additionalInfo) => {
compilation.fileDependencies.addAll(
resolveContext.fileDependencies
);
compilation.contextDependencies.addAll(
resolveContext.contextDependencies
);
compilation.missingDependencies.addAll(
resolveContext.missingDependencies
);
let details;
if (err) {
details = `Failed to resolve: ${err}.`;
} else if (!result) {
details = `Resolved to void.`;
} else if (!additionalInfo) {
details = `No additional info provided from resolver.`;
} else {
const info = /** @type {any} */ (additionalInfo);
const descriptionFileData = info.descriptionFileData;
if (!descriptionFileData) {
details =
"No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config.";
} else if (!descriptionFileData.version) {
details =
"No version in description file (usually package.json). Add version to description file, or manually specify version in shared config.";
} else if (
descriptionFileData.name &&
config.import !== descriptionFileData.name &&
!config.import.startsWith(`${descriptionFileData.name}/`)
) {
details = `Invalid name in description file (usually package.json): ${descriptionFileData.name}. Check location of description file, update name in description file, add missing description file to the package, or manually specify version in shared config.`;
} else {
version = parseVersion(descriptionFileData.version);
}
}
if (!version) {
const error = new WebpackError(
`No version specified and unable to automatically determine one. ${details}`
);
error.file = `shared module ${name} -> ${config.import}`;
compilation.warnings.push(error);
}
addModule();
}
);
} }
); compilationData.set(compilation, resolvedProvideMap);
} const provideModule = (key, config, resource, resourceResolveData) => {
let version = config.version;
if (version === undefined) {
let details = "";
if (!resourceResolveData) {
details = `No resolve data provided from resolver.`;
} else {
const descriptionFileData =
resourceResolveData.descriptionFileData;
if (!descriptionFileData) {
details =
"No description file (usually package.json) found. Add description file with name and version, or manually specify version in shared config.";
} else if (!descriptionFileData.version) {
details =
"No version in description file (usually package.json). Add version to description file, or manually specify version in shared config.";
} else {
version = parseVersion(descriptionFileData.version);
}
}
if (!version) {
const error = new WebpackError(
`No version specified and unable to automatically determine one. ${details}`
);
error.file = `shared module ${key} -> ${resource}`;
compilation.warnings.push(error);
}
}
resolvedProvideMap.set(resource, {
config,
version
});
};
normalModuleFactory.hooks.module.tap(
"ProvideSharedPlugin",
(module, { resource, resourceResolveData }, { request }) => {
if (resolvedProvideMap.has(resource)) {
return module;
}
{
const config = matchProvides.get(request);
if (config !== undefined) {
provideModule(request, config, resource, resourceResolveData);
}
}
for (const [prefix, config] of prefixMatchProvides) {
if (request.startsWith(prefix)) {
const remainder = request.slice(prefix.length);
provideModule(
resource,
{
...config,
shareKey: config.shareKey + remainder
},
resource,
resourceResolveData
);
}
}
return module;
}
);
}
);
compiler.hooks.finishMake.tapPromise("ProvideSharedPlugin", compilation => {
const resolvedProvideMap = compilationData.get(compilation);
if (!resolvedProvideMap) return Promise.resolve();
return Promise.all(
Array.from(
resolvedProvideMap,
([resource, { config, version }]) =>
new Promise((resolve, reject) => {
compilation.addInclude(
compiler.context,
new ProvideDependency(
config.shareScope,
config.shareKey,
version || false,
resource,
config.eager
),
{
name: undefined
},
err => {
if (err) return reject(err);
resolve();
}
);
})
)
).then(() => {});
});
compiler.hooks.compilation.tap( compiler.hooks.compilation.tap(
"ProvideSharedPlugin", "ProvideSharedPlugin",

View File

@ -47,10 +47,11 @@ class SharePlugin {
const consumes = sharedOptions.map(([key, options]) => ({ const consumes = sharedOptions.map(([key, options]) => ({
[key]: { [key]: {
import: options.import, import: options.import,
shareKey: options.shareKey, shareKey: options.shareKey || key,
requiredVersion: options.requiredVersion, requiredVersion: options.requiredVersion,
strictVersion: options.strictVersion, strictVersion: options.strictVersion,
singleton: options.singleton, singleton: options.singleton,
packageName: options.packageName,
eager: options.eager eager: options.eager
} }
})); }));
@ -58,8 +59,8 @@ class SharePlugin {
const provides = sharedOptions const provides = sharedOptions
.filter(([, options]) => options.import !== false) .filter(([, options]) => options.import !== false)
.map(([key, options]) => ({ .map(([key, options]) => ({
[options.shareKey || key]: { [options.import || key]: {
import: options.import || key, shareKey: options.shareKey || key,
version: options.version, version: options.version,
eager: options.eager eager: options.eager
} }

View File

@ -0,0 +1,86 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const ModuleNotFoundError = require("../ModuleNotFoundError");
const LazySet = require("../util/LazySet");
/** @typedef {import("../Compilation")} Compilation */
/**
* @template T
* @typedef {Object} MatchedConfigs
* @property {Map<string, T>} resolved
* @property {Map<string, T>} unresolved
* @property {Map<string, T>} prefixed
*/
/**
* @template T
* @param {Compilation} compilation the compilation
* @param {[string, T][]} configs to be processed configs
* @returns {Promise<MatchedConfigs<T>>} resolved matchers
*/
exports.resolveMatchedConfigs = (compilation, configs) => {
/** @type {Map<string, T>} */
const resolved = new Map();
/** @type {Map<string, T>} */
const unresolved = new Map();
/** @type {Map<string, T>} */
const prefixed = new Map();
const resolveContext = {
/** @type {LazySet<string>} */
fileDependencies: new LazySet(),
/** @type {LazySet<string>} */
contextDependencies: new LazySet(),
/** @type {LazySet<string>} */
missingDependencies: new LazySet()
};
const resolver = compilation.resolverFactory.get("normal");
const context = compilation.compiler.context;
return Promise.all(
configs.map(([request, config]) => {
if (/^\.\.?(\/|$)/.test(request)) {
// relative request
return new Promise(resolve => {
resolver.resolve(
{},
context,
request,
resolveContext,
(err, result) => {
if (err) {
compilation.errors.push(
new ModuleNotFoundError(null, err, {
name: `shared module ${request}`
})
);
return resolve();
}
resolved.set(result, config);
resolve();
}
);
});
} else if (/^(\/|[A-Za-z]:\\|\\\\)/.test(request)) {
// absolute path
resolved.set(request, config);
} else if (request.endsWith("/")) {
// module request prefix
prefixed.set(request, config);
} else {
// module request
unresolved.set(request, config);
}
})
).then(() => {
compilation.contextDependencies.addAll(resolveContext.contextDependencies);
compilation.fileDependencies.addAll(resolveContext.fileDependencies);
compilation.missingDependencies.addAll(resolveContext.missingDependencies);
return { resolved, unresolved, prefixed };
});
};

View File

@ -5,11 +5,16 @@
"use strict"; "use strict";
const { join, dirname, readJson } = require("../util/fs");
/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
/** /**
* @param {string} version version as string * @param {string} version version as string
* @returns {(number|string)[]} version as array * @returns {(number|string)[]} version as array
*/ */
exports.parseRequiredVersion = version => { exports.parseRequiredVersion = version => {
if (version === "*") return [];
let fuzzyStart = Infinity; let fuzzyStart = Infinity;
if (version.startsWith(">=")) { if (version.startsWith(">=")) {
fuzzyStart = 0; fuzzyStart = 0;
@ -40,6 +45,7 @@ exports.parseVersion = version => {
*/ */
exports.versionToString = version => { exports.versionToString = version => {
if (!version) return "(unknown)"; if (!version) return "(unknown)";
if (version.length === 0) return "*";
const info = version.map(value => const info = version.map(value =>
typeof value !== "string" typeof value !== "string"
? { ? {
@ -96,8 +102,82 @@ exports.versionToString = version => {
* @returns {boolean} true, if it looks like a version * @returns {boolean} true, if it looks like a version
*/ */
exports.isRequiredVersion = str => { exports.isRequiredVersion = str => {
if (str === "*") return true;
if (/&&|\|\|/.test(str)) return false;
if (str.startsWith("^")) return true; if (str.startsWith("^")) return true;
if (str.startsWith("~")) return true; if (str.startsWith("~")) return true;
if (str.startsWith(">=")) return true; if (str.startsWith(">=")) return true;
return /^\d/.test(str); return /^\d/.test(str);
}; };
/**
*
* @param {InputFileSystem} fs file system
* @param {string} directory directory to start looking into
* @param {string[]} descriptionFiles possible description filenames
* @param {function(Error=, {data: object, path: string}=): void} callback callback
*/
const getDescriptionFile = (fs, directory, descriptionFiles, callback) => {
let i = 0;
const tryLoadCurrent = () => {
if (i >= descriptionFiles.length) {
const parentDirectory = dirname(fs, directory);
if (!parentDirectory || parentDirectory === directory) return callback();
return getDescriptionFile(
fs,
parentDirectory,
descriptionFiles,
callback
);
}
const filePath = join(fs, directory, descriptionFiles[i]);
readJson(fs, filePath, (err, data) => {
if (err) {
if ("code" in err && err.code === "ENOENT") {
i++;
return tryLoadCurrent();
}
return callback(err);
}
if (!data || typeof data !== "object" || Array.isArray(data)) {
return callback(
new Error(`Description file ${filePath} is not an object`)
);
}
callback(null, { data, path: filePath });
});
};
tryLoadCurrent();
};
exports.getDescriptionFile = getDescriptionFile;
exports.getRequiredVersionFromDescriptionFile = (data, packageName) => {
if (
data.optionalDependencies &&
typeof data.optionalDependencies === "object" &&
packageName in data.optionalDependencies
) {
return data.optionalDependencies[packageName];
}
if (
data.dependencies &&
typeof data.dependencies === "object" &&
packageName in data.dependencies
) {
return data.dependencies[packageName];
}
if (
data.peerDependencies &&
typeof data.peerDependencies === "object" &&
packageName in data.peerDependencies
) {
return data.peerDependencies[packageName];
}
if (
data.devDependencies &&
typeof data.devDependencies === "object" &&
packageName in data.devDependencies
) {
return data.devDependencies[packageName];
}
};

View File

@ -14,6 +14,7 @@ const path = require("path");
/** @typedef {function(NodeJS.ErrnoException=, string[]=): void} StringArrayCallback */ /** @typedef {function(NodeJS.ErrnoException=, string[]=): void} StringArrayCallback */
/** @typedef {function(NodeJS.ErrnoException=, string=): void} StringCallback */ /** @typedef {function(NodeJS.ErrnoException=, string=): void} StringCallback */
/** @typedef {function(NodeJS.ErrnoException=, NodeFsStats=): void} StatsCallback */ /** @typedef {function(NodeJS.ErrnoException=, NodeFsStats=): void} StatsCallback */
/** @typedef {function((NodeJS.ErrnoException | Error)=, any=): void} ReadJsonCallback */
/** /**
* @typedef {Object} OutputFileSystem * @typedef {Object} OutputFileSystem
@ -29,6 +30,7 @@ const path = require("path");
/** /**
* @typedef {Object} InputFileSystem * @typedef {Object} InputFileSystem
* @property {function(string, BufferCallback): void} readFile * @property {function(string, BufferCallback): void} readFile
* @property {(function(string, ReadJsonCallback): void)=} readJson
* @property {function(string, StringArrayCallback): void} readdir * @property {function(string, StringArrayCallback): void} readdir
* @property {function(string, StatsCallback): void} stat * @property {function(string, StatsCallback): void} stat
* @property {(function(string, StringCallback): void)=} realpath * @property {(function(string, StringCallback): void)=} realpath
@ -181,3 +183,24 @@ const mkdirpSync = (fs, p) => {
} }
}; };
exports.mkdirpSync = mkdirpSync; exports.mkdirpSync = mkdirpSync;
/**
* @param {InputFileSystem} fs a file system
* @param {string} p an absolute path
* @param {ReadJsonCallback} callback callback
* @returns {void}
*/
const readJson = (fs, p, callback) => {
if ("readJson" in fs) return fs.readJson(p, callback);
fs.readFile(p, (err, buf) => {
if (err) return callback(err);
let data;
try {
data = JSON.parse(buf.toString("utf-8"));
} catch (e) {
return callback(e);
}
return callback(null, data);
});
};
exports.readJson = readJson;

View File

@ -63,8 +63,12 @@ module.exports = {
require("../dependencies/ContextElementDependency"), require("../dependencies/ContextElementDependency"),
"dependencies/CriticalDependencyWarning": () => "dependencies/CriticalDependencyWarning": () =>
require("../dependencies/CriticalDependencyWarning"), require("../dependencies/CriticalDependencyWarning"),
"dependencies/DelegatedSourceDependency": () =>
require("../dependencies/DelegatedSourceDependency"),
"dependencies/DllEntryDependency": () => "dependencies/DllEntryDependency": () =>
require("../dependencies/DllEntryDependency"), require("../dependencies/DllEntryDependency"),
"dependencies/EntryDependency": () =>
require("../dependencies/EntryDependency"),
"dependencies/ExportsInfoDependency": () => "dependencies/ExportsInfoDependency": () =>
require("../dependencies/ExportsInfoDependency"), require("../dependencies/ExportsInfoDependency"),
"dependencies/HarmonyAcceptDependency": () => "dependencies/HarmonyAcceptDependency": () =>
@ -87,8 +91,6 @@ module.exports = {
require("../dependencies/HarmonyImportSpecifierDependency"), require("../dependencies/HarmonyImportSpecifierDependency"),
"dependencies/ImportContextDependency": () => "dependencies/ImportContextDependency": () =>
require("../dependencies/ImportContextDependency"), require("../dependencies/ImportContextDependency"),
"dependencies/ImportDependenciesBlock": () =>
require("../dependencies/ImportDependenciesBlock"),
"dependencies/ImportDependency": () => "dependencies/ImportDependency": () =>
require("../dependencies/ImportDependency"), require("../dependencies/ImportDependency"),
"dependencies/ImportEagerDependency": () => "dependencies/ImportEagerDependency": () =>
@ -144,7 +146,9 @@ module.exports = {
require("../dependencies/WebAssemblyImportDependency"), require("../dependencies/WebAssemblyImportDependency"),
"optimize/ConcatenatedModule": () => "optimize/ConcatenatedModule": () =>
require("../optimize/ConcatenatedModule"), require("../optimize/ConcatenatedModule"),
DelegatedModule: () => require("../DelegatedModule"),
DependenciesBlock: () => require("../DependenciesBlock"), DependenciesBlock: () => require("../DependenciesBlock"),
DllModule: () => require("../DllModule"),
ExternalModule: () => require("../ExternalModule"), ExternalModule: () => require("../ExternalModule"),
Module: () => require("../Module"), Module: () => require("../Module"),
ModuleBuildError: () => require("../ModuleBuildError"), ModuleBuildError: () => require("../ModuleBuildError"),

View File

@ -44,9 +44,11 @@ class WasmFinalizeExportsPlugin {
connection.dependency connection.dependency
); );
for (const names of referencedExports) { for (const info of referencedExports) {
const names = Array.isArray(info) ? info : info.name;
if (names.length === 0) continue; if (names.length === 0) continue;
const name = names[0]; const name = names[0];
if (typeof name === "object") continue;
// 3. and uses a func with an incompatible JS signature // 3. and uses a func with an incompatible JS signature
if ( if (
Object.prototype.hasOwnProperty.call( Object.prototype.hasOwnProperty.call(

View File

@ -15,7 +15,6 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
constructor(runtimeRequirements, jsonpScript, linkPreload, linkPrefetch) { constructor(runtimeRequirements, jsonpScript, linkPreload, linkPrefetch) {
super("jsonp chunk loading", 10); super("jsonp chunk loading", 10);
this.runtimeRequirements = runtimeRequirements; this.runtimeRequirements = runtimeRequirements;
this.jsonpScript = jsonpScript;
this.linkPreload = linkPreload; this.linkPreload = linkPreload;
this.linkPrefetch = linkPrefetch; this.linkPrefetch = linkPrefetch;
} }
@ -24,7 +23,7 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
* @returns {string} runtime code * @returns {string} runtime code
*/ */
generate() { generate() {
const { compilation, chunk, jsonpScript, linkPreload, linkPrefetch } = this; const { compilation, chunk, linkPreload, linkPrefetch } = this;
const { runtimeTemplate, chunkGraph, outputOptions } = compilation; const { runtimeTemplate, chunkGraph, outputOptions } = compilation;
const fn = RuntimeGlobals.ensureChunkHandlers; const fn = RuntimeGlobals.ensureChunkHandlers;
const withLoading = this.runtimeRequirements.has( const withLoading = this.runtimeRequirements.has(
@ -100,20 +99,31 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
"", "",
"// start chunk loading", "// start chunk loading",
`var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`, `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkScriptFilename}(chunkId);`,
"// create error before stack unwound to get useful stacktrace later",
"var error = new Error();",
`var loadingEnded = ${runtimeTemplate.basicFunction( `var loadingEnded = ${runtimeTemplate.basicFunction(
"", "event",
[ [
`if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`, `if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
Template.indent([ Template.indent([
"installedChunkData = installedChunks[chunkId];", "installedChunkData = installedChunks[chunkId];",
"if(installedChunkData !== 0) installedChunks[chunkId] = undefined;", "if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
"if(installedChunkData) return installedChunkData[1];" "if(installedChunkData) {",
Template.indent([
"var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
"var realSrc = event && event.target && event.target.src;",
"error.message = 'Loading chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
"error.name = 'ChunkLoadError';",
"error.type = errorType;",
"error.request = realSrc;",
"installedChunkData[1](error);"
]),
"}"
]), ]),
"}" "}"
] ]
)};`, )};`,
jsonpScript.call("", chunk), `${RuntimeGlobals.loadScript}(url, loadingEnded, "chunk-" + chunkId);`
"document.head.appendChild(script);"
]), ]),
"} else installedChunks[chunkId] = 0;" "} else installedChunks[chunkId] = 0;"
]), ]),
@ -174,16 +184,23 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
"waitingUpdateResolves[chunkId] = resolve;", "waitingUpdateResolves[chunkId] = resolve;",
"// start update chunk loading", "// start update chunk loading",
`var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`, `var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkUpdateScriptFilename}(chunkId);`,
`var loadingEnded = ${runtimeTemplate.basicFunction("", [ "// create error before stack unwound to get useful stacktrace later",
"var error = new Error();",
`var loadingEnded = ${runtimeTemplate.basicFunction("event", [
"if(waitingUpdateResolves[chunkId]) {", "if(waitingUpdateResolves[chunkId]) {",
Template.indent([ Template.indent([
"waitingUpdateResolves[chunkId] = undefined", "waitingUpdateResolves[chunkId] = undefined",
"return reject;" "var errorType = event && (event.type === 'load' ? 'missing' : event.type);",
"var realSrc = event && event.target && event.target.src;",
"error.message = 'Loading hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realSrc + ')';",
"error.name = 'ChunkLoadError';",
"error.type = errorType;",
"error.request = realSrc;",
"reject(error);"
]), ]),
"}" "}"
])};`, ])};`,
jsonpScript.call("", chunk), `${RuntimeGlobals.loadScript}(url, loadingEnded);`
"document.head.appendChild(script);"
] ]
)});` )});`
]), ]),

View File

@ -242,6 +242,7 @@ class JsonpTemplatePlugin {
onceForChunkSet.add(chunk); onceForChunkSet.add(chunk);
set.add(RuntimeGlobals.moduleFactoriesAddOnly); set.add(RuntimeGlobals.moduleFactoriesAddOnly);
set.add(RuntimeGlobals.hasOwnProperty); set.add(RuntimeGlobals.hasOwnProperty);
set.add(RuntimeGlobals.loadScript);
compilation.addRuntimeModule( compilation.addRuntimeModule(
chunk, chunk,
new JsonpChunkLoadingRuntimeModule( new JsonpChunkLoadingRuntimeModule(

View File

@ -1,6 +1,6 @@
{ {
"name": "webpack", "name": "webpack",
"version": "5.0.0-beta.16", "version": "5.0.0-beta.17",
"author": "Tobias Koppers @sokra", "author": "Tobias Koppers @sokra",
"description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.", "description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.",
"license": "MIT", "license": "MIT",
@ -30,6 +30,7 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.7.2", "@babel/core": "^7.7.2",
"@babel/preset-react": "^7.10.1",
"@types/jest": "^25.1.5", "@types/jest": "^25.1.5",
"@types/node": "^12.6.9", "@types/node": "^12.6.9",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
@ -80,7 +81,7 @@
"strip-ansi": "^6.0.0", "strip-ansi": "^6.0.0",
"style-loader": "^1.0.0", "style-loader": "^1.0.0",
"toml": "^3.0.0", "toml": "^3.0.0",
"tooling": "webpack/tooling#v1.7.0", "tooling": "webpack/tooling#v1.8.0",
"ts-loader": "^6.0.4", "ts-loader": "^6.0.4",
"typescript": "^3.9.2", "typescript": "^3.9.2",
"url-loader": "^4.1.0", "url-loader": "^4.1.0",

View File

@ -254,8 +254,7 @@
"library": { "library": {
"$ref": "#/definitions/LibraryOptions" "$ref": "#/definitions/LibraryOptions"
} }
}, }
"required": ["import"]
}, },
"EntryDynamic": { "EntryDynamic": {
"description": "A Function returning an entry object, an entry string, an entry array or a promise to these things.", "description": "A Function returning an entry object, an entry string, an entry array or a promise to these things.",
@ -312,8 +311,7 @@
"$ref": "#/definitions/EntryDescription" "$ref": "#/definitions/EntryDescription"
} }
] ]
}, }
"minProperties": 1
}, },
"EntryStatic": { "EntryStatic": {
"description": "A static entry description.", "description": "A static entry description.",
@ -336,8 +334,7 @@
"$ref": "#/definitions/EntryDescriptionNormalized" "$ref": "#/definitions/EntryDescriptionNormalized"
} }
] ]
}, }
"minProperties": 1
}, },
"EntryUnnamed": { "EntryUnnamed": {
"description": "An entry point without name.", "description": "An entry point without name.",
@ -467,7 +464,8 @@
"jsonp", "jsonp",
"system", "system",
"promise", "promise",
"import" "import",
"script"
] ]
}, },
"FileCacheOptions": { "FileCacheOptions": {

View File

@ -43,9 +43,18 @@
} }
] ]
}, },
"packageName": {
"description": "Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.",
"type": "string",
"minLength": 1
},
"requiredVersion": { "requiredVersion": {
"description": "Version requirement from module in share scope.", "description": "Version requirement from module in share scope.",
"anyOf": [ "anyOf": [
{
"description": "No version requirement check.",
"enum": [false]
},
{ {
"description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.", "description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.",
"type": "string" "type": "string"

View File

@ -20,7 +20,8 @@
"jsonp", "jsonp",
"system", "system",
"promise", "promise",
"import" "import",
"script"
] ]
}, },
"Remotes": { "Remotes": {

View File

@ -103,7 +103,8 @@
"jsonp", "jsonp",
"system", "system",
"promise", "promise",
"import" "import",
"script"
] ]
}, },
"LibraryCustomUmdCommentObject": { "LibraryCustomUmdCommentObject": {
@ -363,9 +364,18 @@
} }
] ]
}, },
"packageName": {
"description": "Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.",
"type": "string",
"minLength": 1
},
"requiredVersion": { "requiredVersion": {
"description": "Version requirement from module in share scope.", "description": "Version requirement from module in share scope.",
"anyOf": [ "anyOf": [
{
"description": "No version requirement check.",
"enum": [false]
},
{ {
"description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.", "description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.",
"type": "string" "type": "string"

View File

@ -43,9 +43,18 @@
} }
] ]
}, },
"packageName": {
"description": "Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.",
"type": "string",
"minLength": 1
},
"requiredVersion": { "requiredVersion": {
"description": "Version requirement from module in share scope.", "description": "Version requirement from module in share scope.",
"anyOf": [ "anyOf": [
{
"description": "No version requirement check.",
"enum": [false]
},
{ {
"description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.", "description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.",
"type": "string" "type": "string"

View File

@ -1,7 +1,7 @@
{ {
"definitions": { "definitions": {
"Provides": { "Provides": {
"description": "Modules that should be provided as shared modules to the share scope. When provided, property name is used as share key, otherwise share key is automatically inferred from request.", "description": "Modules that should be provided as shared modules to the share scope. When provided, property name is used to match modules, otherwise this is automatically inferred from share key.",
"anyOf": [ "anyOf": [
{ {
"type": "array", "type": "array",
@ -31,8 +31,10 @@
"description": "Include the provided module directly instead behind an async request. This allows to use this shared module in initial load too. All possible shared modules need to be eager too.", "description": "Include the provided module directly instead behind an async request. This allows to use this shared module in initial load too. All possible shared modules need to be eager too.",
"type": "boolean" "type": "boolean"
}, },
"import": { "shareKey": {
"$ref": "#/definitions/ProvidesItem" "description": "Key in the share scope under which the shared modules should be stored.",
"type": "string",
"minLength": 1
}, },
"shareScope": { "shareScope": {
"description": "Share scope name.", "description": "Share scope name.",
@ -55,11 +57,10 @@
} }
] ]
} }
}, }
"required": ["import"]
}, },
"ProvidesItem": { "ProvidesItem": {
"description": "Request to a module that should be provided as shared module to the share scope.", "description": "Request to a module that should be provided as shared module to the share scope (will be resolved when relative).",
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
}, },

View File

@ -43,9 +43,18 @@
} }
] ]
}, },
"packageName": {
"description": "Package name to determine required version from description file. This is only needed when package name can't be automatically determined from request.",
"type": "string",
"minLength": 1
},
"requiredVersion": { "requiredVersion": {
"description": "Version requirement from module in share scope.", "description": "Version requirement from module in share scope.",
"anyOf": [ "anyOf": [
{
"description": "No version requirement check.",
"enum": [false]
},
{ {
"description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.", "description": "Version as string. Can be prefixed with '^' or '~' for minimum matches. Each part of the version should be separated by a dot '.'.",
"type": "string" "type": "string"

View File

@ -0,0 +1,10 @@
const path = require("path");
const { describeCases } = require("./ConfigTestCases.template");
describeCases({
name: "ConfigCacheTestCases",
cache: {
type: "filesystem",
managedPaths: [path.resolve(__dirname, "../node_modules")]
}
});

View File

@ -0,0 +1,349 @@
"use strict";
const path = require("path");
const fs = require("graceful-fs");
const vm = require("vm");
const rimraf = require("rimraf");
const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const deprecationTracking = require("./helpers/deprecationTracking");
const FakeDocument = require("./helpers/FakeDocument");
const webpack = require("..");
const prepareOptions = require("./helpers/prepareOptions");
const casesPath = path.join(__dirname, "configCases");
const categories = fs.readdirSync(casesPath).map(cat => {
return {
name: cat,
tests: fs
.readdirSync(path.join(casesPath, cat))
.filter(folder => !folder.startsWith("_"))
.sort()
};
});
const describeCases = config => {
describe(config.name, () => {
jest.setTimeout(20000);
for (const category of categories) {
describe(category.name, () => {
for (const testName of category.tests) {
describe(testName, function () {
const testDirectory = path.join(casesPath, category.name, testName);
const filterPath = path.join(testDirectory, "test.filter.js");
if (fs.existsSync(filterPath) && !require(filterPath)()) {
describe.skip(testName, () => {
it("filtered", () => {});
});
return;
}
const outBaseDir = path.join(__dirname, "js");
const testSubPath = path.join(config.name, category.name, testName);
const outputDirectory = path.join(outBaseDir, testSubPath);
const cacheDirectory = path.join(outBaseDir, ".cache", testSubPath);
let options, optionsArr, testConfig;
beforeAll(() => {
options = prepareOptions(
require(path.join(testDirectory, "webpack.config.js")),
{ testPath: outputDirectory }
);
optionsArr = [].concat(options);
optionsArr.forEach((options, idx) => {
if (!options.context) options.context = testDirectory;
if (!options.mode) options.mode = "production";
if (!options.optimization) options.optimization = {};
if (options.optimization.minimize === undefined)
options.optimization.minimize = false;
if (!options.entry) options.entry = "./index.js";
if (!options.target) options.target = "async-node";
if (!options.output) options.output = {};
if (!options.output.path) options.output.path = outputDirectory;
if (typeof options.output.pathinfo === "undefined")
options.output.pathinfo = true;
if (!options.output.filename)
options.output.filename = "bundle" + idx + ".js";
if (config.cache) {
options.cache = {
cacheDirectory,
name: `config-${idx}`,
...config.cache
};
}
});
testConfig = {
findBundle: function (i, options) {
const ext = path.extname(options.output.filename);
if (
fs.existsSync(
path.join(options.output.path, "bundle" + i + ext)
)
) {
return "./bundle" + i + ext;
}
},
timeout: 30000
};
try {
// try to load a test file
testConfig = Object.assign(
testConfig,
require(path.join(testDirectory, "test.config.js"))
);
} catch (e) {
// ignored
}
if (testConfig.timeout) setDefaultTimeout(testConfig.timeout);
});
beforeAll(() => {
rimraf.sync(cacheDirectory);
});
const handleFatalError = (err, done) => {
const fakeStats = {
errors: [
{
message: err.message,
stack: err.stack
}
]
};
if (
checkArrayExpectation(
testDirectory,
fakeStats,
"error",
"Error",
done
)
) {
return;
}
// Wait for uncaught errors to occur
setTimeout(done, 200);
return;
};
if (config.cache) {
it(`${testName} should pre-compile to fill disk cache (1st)`, done => {
rimraf.sync(outputDirectory);
fs.mkdirSync(outputDirectory, { recursive: true });
const deprecationTracker = deprecationTracking.start();
webpack(options, err => {
deprecationTracker();
if (err) return handleFatalError(err, done);
done();
});
}, 60000);
it(`${testName} should pre-compile to fill disk cache (2nd)`, done => {
rimraf.sync(outputDirectory);
fs.mkdirSync(outputDirectory, { recursive: true });
const deprecationTracker = deprecationTracking.start();
webpack(options, err => {
deprecationTracker();
if (err) return handleFatalError(err, done);
done();
});
}, 20000);
}
it(`${testName} should compile`, done => {
rimraf.sync(outputDirectory);
fs.mkdirSync(outputDirectory, { recursive: true });
const deprecationTracker = deprecationTracking.start();
webpack(options, (err, stats) => {
const deprecations = deprecationTracker();
if (err) return handleFatalError(err, done);
const statOptions = {
preset: "verbose",
colors: false
};
fs.mkdirSync(outputDirectory, { recursive: true });
fs.writeFileSync(
path.join(outputDirectory, "stats.txt"),
stats.toString(statOptions),
"utf-8"
);
const jsonStats = stats.toJson({
errorDetails: true
});
fs.writeFileSync(
path.join(outputDirectory, "stats.json"),
JSON.stringify(jsonStats, null, 2),
"utf-8"
);
if (
checkArrayExpectation(
testDirectory,
jsonStats,
"error",
"Error",
done
)
) {
return;
}
if (
checkArrayExpectation(
testDirectory,
jsonStats,
"warning",
"Warning",
done
)
) {
return;
}
if (
checkArrayExpectation(
testDirectory,
{ deprecations },
"deprecation",
"Deprecation",
done
)
) {
return;
}
const globalContext = {
console: console,
expect: expect,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
document: new FakeDocument(),
location: {
href: "https://test.cases/path/index.html",
origin: "https://test.cases",
toString() {
return "https://test.cases/path/index.html";
}
}
};
const requireCache = Object.create(null);
function _require(currentDirectory, options, module) {
if (Array.isArray(module) || /^\.\.?\//.test(module)) {
let content;
let p;
if (Array.isArray(module)) {
p = path.join(currentDirectory, ".array-require.js");
content = `module.exports = (${module
.map(arg => {
return `require(${JSON.stringify(`./${arg}`)})`;
})
.join(", ")});`;
} else {
p = path.join(currentDirectory, module);
content = fs.readFileSync(p, "utf-8");
}
if (p in requireCache) {
return requireCache[p].exports;
}
const m = {
exports: {}
};
requireCache[p] = m;
let runInNewContext = false;
const moduleScope = {
require: _require.bind(null, path.dirname(p), options),
importScripts: _require.bind(
null,
path.dirname(p),
options
),
module: m,
exports: m.exports,
__dirname: path.dirname(p),
__filename: p,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
expect,
jest,
_globalAssign: { expect },
__STATS__: jsonStats,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
value: "Module"
});
return m;
}
};
if (
options.target === "web" ||
options.target === "webworker"
) {
moduleScope.window = globalContext;
moduleScope.self = globalContext;
runInNewContext = true;
}
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
const args = Object.keys(moduleScope).join(", ");
if (!runInNewContext)
content = `Object.assign(global, _globalAssign); ${content}`;
const code = `(function({${args}}) {${content}\n})`;
const fn = runInNewContext
? vm.runInNewContext(code, globalContext, p)
: vm.runInThisContext(code, p);
fn.call(m.exports, moduleScope);
return m.exports;
} else if (
testConfig.modules &&
module in testConfig.modules
) {
return testConfig.modules[module];
} else return require(module);
}
let filesCount = 0;
if (testConfig.noTests) return process.nextTick(done);
if (testConfig.beforeExecute) testConfig.beforeExecute();
const results = [];
for (let i = 0; i < optionsArr.length; i++) {
const bundlePath = testConfig.findBundle(i, optionsArr[i]);
if (bundlePath) {
filesCount++;
results.push(
_require(outputDirectory, optionsArr[i], bundlePath)
);
}
}
// give a free pass to compilation that generated an error
if (
!jsonStats.errors.length &&
filesCount !== optionsArr.length
) {
return done(
new Error(
"Should have found at least one bundle file per webpack config"
)
);
}
Promise.all(results)
.then(() => {
if (testConfig.afterExecute) testConfig.afterExecute();
if (getNumberOfTests() < filesCount) {
return done(new Error("No tests exported by test case"));
}
done();
})
.catch(done);
});
});
const {
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
setDefaultTimeout,
getNumberOfTests
} = createLazyTestEnv(jasmine.getEnv(), 10000);
});
}
});
}
});
};
exports.describeCases = describeCases;

View File

@ -1,325 +1,5 @@
"use strict"; const { describeCases } = require("./ConfigTestCases.template");
const path = require("path"); describeCases({
const fs = require("graceful-fs"); name: "ConfigTestCases"
const vm = require("vm");
const rimraf = require("rimraf");
const checkArrayExpectation = require("./checkArrayExpectation");
const createLazyTestEnv = require("./helpers/createLazyTestEnv");
const deprecationTracking = require("./helpers/deprecationTracking");
const FakeDocument = require("./helpers/FakeDocument");
const webpack = require("..");
const prepareOptions = require("./helpers/prepareOptions");
describe("ConfigTestCases", () => {
const casesPath = path.join(__dirname, "configCases");
let categories = fs.readdirSync(casesPath);
jest.setTimeout(20000);
categories = categories.map(cat => {
return {
name: cat,
tests: fs
.readdirSync(path.join(casesPath, cat))
.filter(folder => {
return folder.indexOf("_") < 0;
})
.sort()
.filter(testName => {
const testDirectory = path.join(casesPath, cat, testName);
const filterPath = path.join(testDirectory, "test.filter.js");
if (fs.existsSync(filterPath) && !require(filterPath)()) {
describe.skip(testName, () => {
it("filtered", () => {});
});
return false;
}
return true;
})
};
});
categories.forEach(category => {
describe(category.name, () => {
category.tests.forEach(testName => {
describe(testName, function () {
const testDirectory = path.join(casesPath, category.name, testName);
const outputDirectory = path.join(
__dirname,
"js",
"config",
category.name,
testName
);
it(
testName + " should compile",
() =>
new Promise((resolve, reject) => {
const done = err => {
if (err) return reject(err);
resolve();
};
rimraf.sync(outputDirectory);
fs.mkdirSync(outputDirectory, { recursive: true });
const options = prepareOptions(
require(path.join(testDirectory, "webpack.config.js")),
{ testPath: outputDirectory }
);
const optionsArr = [].concat(options);
optionsArr.forEach((options, idx) => {
if (!options.context) options.context = testDirectory;
if (!options.mode) options.mode = "production";
if (!options.optimization) options.optimization = {};
if (options.optimization.minimize === undefined)
options.optimization.minimize = false;
if (!options.entry) options.entry = "./index.js";
if (!options.target) options.target = "async-node";
if (!options.output) options.output = {};
if (!options.output.path)
options.output.path = outputDirectory;
if (typeof options.output.pathinfo === "undefined")
options.output.pathinfo = true;
if (!options.output.filename)
options.output.filename = "bundle" + idx + ".js";
});
let testConfig = {
findBundle: function (i, options) {
const ext = path.extname(options.output.filename);
if (
fs.existsSync(
path.join(options.output.path, "bundle" + i + ext)
)
) {
return "./bundle" + i + ext;
}
},
timeout: 30000
};
try {
// try to load a test file
testConfig = Object.assign(
testConfig,
require(path.join(testDirectory, "test.config.js"))
);
} catch (e) {
// ignored
}
if (testConfig.timeout) setDefaultTimeout(testConfig.timeout);
const deprecationTracker = deprecationTracking.start();
webpack(options, (err, stats) => {
if (err) {
const fakeStats = {
errors: [
{
message: err.message,
stack: err.stack
}
]
};
if (
checkArrayExpectation(
testDirectory,
fakeStats,
"error",
"Error",
done
)
)
return;
// Wait for uncaught errors to occur
return setTimeout(done, 200);
}
const statOptions = {
preset: "verbose",
colors: false
};
fs.mkdirSync(outputDirectory, { recursive: true });
fs.writeFileSync(
path.join(outputDirectory, "stats.txt"),
stats.toString(statOptions),
"utf-8"
);
const jsonStats = stats.toJson({
errorDetails: true
});
fs.writeFileSync(
path.join(outputDirectory, "stats.json"),
JSON.stringify(jsonStats, null, 2),
"utf-8"
);
if (
checkArrayExpectation(
testDirectory,
jsonStats,
"error",
"Error",
done
)
)
return;
if (
checkArrayExpectation(
testDirectory,
jsonStats,
"warning",
"Warning",
done
)
)
return;
const deprecations = deprecationTracker();
if (
checkArrayExpectation(
testDirectory,
{ deprecations },
"deprecation",
"Deprecation",
done
)
)
return;
const globalContext = {
console: console,
expect: expect,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
document: new FakeDocument(),
location: {
href: "https://test.cases/path/index.html",
origin: "https://test.cases",
toString() {
return "https://test.cases/path/index.html";
}
}
};
const requireCache = Object.create(null);
function _require(currentDirectory, options, module) {
if (Array.isArray(module) || /^\.\.?\//.test(module)) {
let content;
let p;
if (Array.isArray(module)) {
p = path.join(currentDirectory, ".array-require.js");
content = `module.exports = (${module
.map(arg => {
return `require(${JSON.stringify(`./${arg}`)})`;
})
.join(", ")});`;
} else {
p = path.join(currentDirectory, module);
content = fs.readFileSync(p, "utf-8");
}
if (p in requireCache) {
return requireCache[p].exports;
}
const m = {
exports: {}
};
requireCache[p] = m;
let runInNewContext = false;
const moduleScope = {
require: _require.bind(null, path.dirname(p), options),
importScripts: _require.bind(
null,
path.dirname(p),
options
),
module: m,
exports: m.exports,
__dirname: path.dirname(p),
__filename: p,
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
expect,
jest,
_globalAssign: { expect },
__STATS__: jsonStats,
nsObj: m => {
Object.defineProperty(m, Symbol.toStringTag, {
value: "Module"
});
return m;
}
};
if (
options.target === "web" ||
options.target === "webworker"
) {
moduleScope.window = globalContext;
moduleScope.self = globalContext;
runInNewContext = true;
}
if (testConfig.moduleScope) {
testConfig.moduleScope(moduleScope);
}
const args = Object.keys(moduleScope).join(", ");
if (!runInNewContext)
content = `Object.assign(global, _globalAssign); ${content}`;
const code = `(function({${args}}) {${content}\n})`;
const fn = runInNewContext
? vm.runInNewContext(code, globalContext, p)
: vm.runInThisContext(code, p);
fn.call(m.exports, moduleScope);
return m.exports;
} else if (
testConfig.modules &&
module in testConfig.modules
) {
return testConfig.modules[module];
} else return require(module);
}
let filesCount = 0;
if (testConfig.noTests) return process.nextTick(done);
if (testConfig.beforeExecute) testConfig.beforeExecute();
const results = [];
for (let i = 0; i < optionsArr.length; i++) {
const bundlePath = testConfig.findBundle(i, optionsArr[i]);
if (bundlePath) {
filesCount++;
results.push(
_require(outputDirectory, optionsArr[i], bundlePath)
);
}
}
// give a free pass to compilation that generated an error
if (
!jsonStats.errors.length &&
filesCount !== optionsArr.length
)
return done(
new Error(
"Should have found at least one bundle file per webpack config"
)
);
Promise.all(results)
.then(() => {
if (testConfig.afterExecute) testConfig.afterExecute();
if (getNumberOfTests() < filesCount) {
return done(
new Error("No tests exported by test case")
);
}
done();
})
.catch(done);
});
})
);
const {
it: _it,
beforeEach: _beforeEach,
afterEach: _afterEach,
setDefaultTimeout,
getNumberOfTests
} = createLazyTestEnv(jasmine.getEnv(), 10000);
});
});
});
});
}); });

View File

@ -138,6 +138,11 @@ const describeCases = config => {
_attrs: {}, _attrs: {},
setAttribute(name, value) { setAttribute(name, value) {
this._attrs[name] = value; this._attrs[name] = value;
},
parentNode: {
removeChild(node) {
// ok
}
} }
}; };
}, },
@ -153,6 +158,7 @@ const describeCases = config => {
}, },
getElementsByTagName(name) { getElementsByTagName(name) {
if (name === "head") return [this.head]; if (name === "head") return [this.head];
if (name === "script") return [];
throw new Error("Not supported"); throw new Error("Not supported");
} }
} }

View File

@ -141,7 +141,8 @@ export default ${files.map((_, i) => `f${i}`).join(" + ")};
"index.js": "index.js":
"export default import('container/src/exposed').then(m => m.default);", "export default import('container/src/exposed').then(m => m.default);",
"exposed.js": "import lib from 'lib'; export default 21 + lib;", "exposed.js": "import lib from 'lib'; export default 21 + lib;",
"lib.js": "export default 21" "lib.js": "export default 20",
"lib2.js": "export default 21"
}; };
await updateSrc(data); await updateSrc(data);
const configAdditions = { const configAdditions = {
@ -157,8 +158,12 @@ export default ${files.map((_, i) => `f${i}`).join(" + ")};
lib: { lib: {
import: "./src/lib", import: "./src/lib",
shareKey: "lib", shareKey: "lib",
version: "1.2.3", version: "1.2.0",
requiredVersion: "^1.0.0" requiredVersion: "^1.0.0"
},
"./src/lib2": {
shareKey: "lib",
version: "1.2.3"
} }
} }
}) })

View File

@ -217,10 +217,10 @@ describe("Stats", () => {
"comparedForEmit": false, "comparedForEmit": false,
"emitted": true, "emitted": true,
"info": Object { "info": Object {
"size": 1865, "size": 2207,
}, },
"name": "entryB.js", "name": "entryB.js",
"size": 1865, "size": 2207,
}, },
], ],
"assetsByChunkName": Object { "assetsByChunkName": Object {

View File

@ -322,4 +322,4 @@ const describeCases = config => {
}); });
}; };
module.exports.describeCases = describeCases; exports.describeCases = describeCases;

View File

@ -490,6 +490,7 @@ Object {
"system", "system",
"promise", "promise",
"import", "import",
"script",
], ],
}, },
], ],

View File

@ -0,0 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ConfigCacheTestCases custom-modules json-custom exported tests should transform toml to json 1`] = `
Object {
"owner": Object {
"bio": "GitHub Cofounder & CEO
Likes tater tots and beer.",
"dob": "1979-05-27T07:32:00.000Z",
"name": "Tom Preston-Werner",
"organization": "GitHub",
},
"title": "TOML Example",
}
`;
exports[`ConfigCacheTestCases records issue-2991 exported tests should write relative paths to records 1`] = `
"{
\\"chunks\\": {
\\"byName\\": {
\\"main\\": 179
},
\\"bySource\\": {
\\"0 main\\": 179
},
\\"usedIds\\": [
179
]
},
\\"modules\\": {
\\"byIdentifier\\": {
\\"./test.js\\": 393,
\\"external \\\\\\"fs\\\\\\"\\": 747,
\\"external \\\\\\"path\\\\\\"\\": 622,
\\"ignored|pkgs/somepackage/foo\\": 713
},
\\"usedIds\\": [
393,
622,
713,
747
]
}
}"
`;
exports[`ConfigCacheTestCases records issue-7339 exported tests should write relative dynamic-require paths to records 1`] = `
"{
\\"chunks\\": {
\\"byName\\": {
\\"main\\": 179
},
\\"bySource\\": {
\\"0 main\\": 179
},
\\"usedIds\\": [
179
]
},
\\"modules\\": {
\\"byIdentifier\\": {
\\"./dependencies/bar.js\\": 379,
\\"./dependencies/foo.js\\": 117,
\\"./dependencies|sync|/^\\\\\\\\.\\\\\\\\/.*$/\\": 412,
\\"./test.js\\": 393,
\\"external \\\\\\"fs\\\\\\"\\": 747,
\\"external \\\\\\"path\\\\\\"\\": 622
},
\\"usedIds\\": [
117,
379,
393,
412,
622,
747
]
}
}"
`;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
export const c = "c";
export const d = "d";
export const longnameforexport = "longnameforexport";
export default "default2";
export const usedExports = __webpack_exports_info__.usedExports;

View File

@ -0,0 +1,7 @@
export const c = "c";
export const d = "d";
export default "default2";
export const usedExports = __webpack_exports_info__.usedExports;

View File

@ -0,0 +1,7 @@
export const a = "a";
export const b = "b";
export default "default";
export const usedExports = __webpack_exports_info__.usedExports;

View File

@ -1,48 +1,56 @@
it("should be able to use eager mode", function() { it("should be able to use eager mode", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "eager" */ "./dir1/" + name); return import(/* webpackMode: "eager" */ "./dir1/" + name);
} }
return testChunkLoading(load, true, true); return testChunkLoading(load, true, true);
}); });
it("should be able to use lazy-once mode", function() { it("should be able to use lazy-once mode", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "lazy-once" */ "./dir2/" + name); return import(/* webpackMode: "lazy-once" */ "./dir2/" + name);
} }
return testChunkLoading(load, false, true); return testChunkLoading(load, false, true);
}); });
it("should be able to use lazy-once mode with name", function() { it("should be able to use lazy-once mode with name", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "lazy-once", webpackChunkName: "name-lazy-once" */ "./dir3/" + name); return import(
/* webpackMode: "lazy-once", webpackChunkName: "name-lazy-once" */ "./dir3/" +
name
);
} }
return testChunkLoading(load, false, true); return testChunkLoading(load, false, true);
}); });
it("should be able to use lazy mode", function() { it("should be able to use lazy mode", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "lazy" */ "./dir4/" + name); return import(/* webpackMode: "lazy" */ "./dir4/" + name);
} }
return testChunkLoading(load, false, false); return testChunkLoading(load, false, false);
}); });
it("should be able to use lazy mode with name", function() { it("should be able to use lazy mode with name", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "lazy", webpackChunkName: "name-lazy" */ "./dir5/" + name); return import(
/* webpackMode: "lazy", webpackChunkName: "name-lazy" */ "./dir5/" + name
);
} }
return testChunkLoading(load, false, false); return testChunkLoading(load, false, false);
}); });
it("should be able to use lazy mode with name and placeholder", function() { it("should be able to use lazy mode with name and placeholder", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "lazy", webpackChunkName: "name-lazy-[request]" */ "./dir6/" + name); return import(
/* webpackMode: "lazy", webpackChunkName: "name-lazy-[request]" */ "./dir6/" +
name
);
} }
return testChunkLoading(load, false, false); return testChunkLoading(load, false, false);
}); });
it("should be able to combine chunks by name", function() { it("should be able to combine chunks by name", function () {
function load(name) { function load(name) {
switch(name) { switch (name) {
case "a": case "a":
return import(/* webpackMode: "eager" */ "./dir7/a"); return import(/* webpackMode: "eager" */ "./dir7/a");
case "b": case "b":
@ -58,19 +66,19 @@ it("should be able to combine chunks by name", function() {
return testChunkLoading(load, false, true); return testChunkLoading(load, false, true);
}); });
it("should be able to use weak mode", function() { it("should be able to use weak mode", function () {
function load(name) { function load(name) {
return import(/* webpackMode: "weak" */ "./dir8/" + name); return import(/* webpackMode: "weak" */ "./dir8/" + name);
} }
require("./dir8/a") // chunks served manually by the user require("./dir8/a"); // chunks served manually by the user
require("./dir8/b") require("./dir8/b");
require("./dir8/c") require("./dir8/c");
return testChunkLoading(load, true, true); return testChunkLoading(load, true, true);
}); });
it("should be able to use weak mode (without context)", function() { it("should be able to use weak mode (without context)", function () {
function load(name) { function load(name) {
switch(name) { switch (name) {
case "a": case "a":
return import(/* webpackMode: "weak" */ "./dir9/a"); return import(/* webpackMode: "weak" */ "./dir9/a");
case "b": case "b":
@ -81,54 +89,134 @@ it("should be able to use weak mode (without context)", function() {
throw new Error("Unexcepted test data"); throw new Error("Unexcepted test data");
} }
} }
require("./dir9/a") // chunks served manually by the user require("./dir9/a"); // chunks served manually by the user
require("./dir9/b") require("./dir9/b");
require("./dir9/c") require("./dir9/c");
return testChunkLoading(load, true, true); return testChunkLoading(load, true, true);
}); });
it("should not find module when mode is weak and chunk not served elsewhere", function() { it("should not find module when mode is weak and chunk not served elsewhere", function () {
var name = "a"; var name = "a";
return import(/* webpackMode: "weak" */ "./dir10/" + name) return import(/* webpackMode: "weak" */ "./dir10/" + name).catch(function (
.catch(function(e) { e
expect(e).toMatchObject({ message: /not available/, code: /MODULE_NOT_FOUND/ }); ) {
expect(e).toMatchObject({
message: /not available/,
code: /MODULE_NOT_FOUND/
}); });
});
}); });
it("should not find module when mode is weak and chunk not served elsewhere (without context)", function() { it("should not find module when mode is weak and chunk not served elsewhere (without context)", function () {
return import(/* webpackMode: "weak" */ "./dir11/a") return import(/* webpackMode: "weak" */ "./dir11/a").catch(function (e) {
.catch(function(e) { expect(e).toMatchObject({
expect(e).toMatchObject({ message: /not available/, code: /MODULE_NOT_FOUND/ }); message: /not available/,
code: /MODULE_NOT_FOUND/
}); });
});
});
it("should contain only one export from webpackExports from module", function () {
return import(/* webpackExports: "usedExports" */ "./dir12/a?1").then(
module => {
expect(module.usedExports).toEqual(["usedExports"]);
}
);
});
it("should contain only webpackExports from module", function () {
return import(
/* webpackExports: ["a", "usedExports", "b"] */ "./dir12/a?2"
).then(module => {
expect(module.usedExports).toEqual(["a", "b", "usedExports"]);
});
});
it("should contain only webpackExports from module in eager mode", function () {
return import(
/*
webpackMode: "eager",
webpackExports: ["a", "usedExports", "b"]
*/ "./dir12/a?3"
).then(module => {
expect(module.usedExports).toEqual(["a", "b", "usedExports"]);
});
});
it("should contain webpackExports from module in weak mode", function () {
require.resolve("./dir12/a?4");
return import(
/*
webpackMode: "weak",
webpackExports: ["a", "usedExports", "b"]
*/ "./dir12/a?4"
).then(module => {
expect(module.usedExports).toEqual(["a", "b", "usedExports"]);
});
});
it("should not mangle webpackExports from module", function () {
return import(/* webpackExports: "longnameforexport" */ "./dir12/a?5").then(
module => {
expect(module).toHaveProperty("longnameforexport");
}
);
});
it("should not mangle default webpackExports from module", function () {
return import(/* webpackExports: "default" */ "./dir12/a?6").then(module => {
expect(module).toHaveProperty("default");
});
});
it("should contain only webpackExports from module in context mode", function () {
const x = "b";
return import(/* webpackExports: "usedExports" */ `./dir13/${x}`).then(
module => {
expect(module.usedExports).toEqual(["usedExports"]);
}
);
}); });
function testChunkLoading(load, expectedSyncInitial, expectedSyncRequested) { function testChunkLoading(load, expectedSyncInitial, expectedSyncRequested) {
var sync = false; var sync = false;
var syncInitial = true; var syncInitial = true;
var p = Promise.all([load("a"), load("b")]).then(function() { var p = Promise.all([load("a"), load("b")]).then(function () {
expect(syncInitial).toBe(expectedSyncInitial); expect(syncInitial).toBe(expectedSyncInitial);
sync = true; sync = true;
var p = Promise.all([ var p = Promise.all([
load("a").then(function(a) { load("a").then(function (a) {
expect(a).toEqual(nsObj({ expect(a).toEqual(
default: "a" nsObj({
})); default: "a"
})
);
expect(sync).toBe(true); expect(sync).toBe(true);
}), }),
load("c").then(function(c) { load("c").then(function (c) {
expect(c).toEqual(nsObj({ expect(c).toEqual(
default: "c" nsObj({
})); default: "c"
})
);
expect(sync).toBe(expectedSyncRequested); expect(sync).toBe(expectedSyncRequested);
}) })
]); ]);
Promise.resolve().then(function(){}).then(function(){}).then(function(){}).then(function(){ Promise.resolve()
sync = false; .then(function () {})
}); .then(function () {})
.then(function () {})
.then(function () {
sync = false;
});
return p; return p;
}); });
Promise.resolve().then(function(){}).then(function(){}).then(function(){}).then(function(){ Promise.resolve()
syncInitial = false; .then(function () {})
}); .then(function () {})
.then(function () {})
.then(function () {
syncInitial = false;
});
return p; return p;
} }

View File

@ -17,7 +17,12 @@ module.exports = {
external: "./container.js" external: "./container.js"
} }
}, },
shared: { react: { version: false } } shared: {
react: {
version: false,
requiredVersion: false
}
}
}) })
] ]
}; };

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"react": "*"
}
}

View File

@ -1,3 +1,4 @@
// eslint-disable-next-line node/no-unpublished-require
const { ModuleFederationPlugin } = require("../../../../").container; const { ModuleFederationPlugin } = require("../../../../").container;
/** @type {import("../../../../").Configuration} */ /** @type {import("../../../../").Configuration} */

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"react": "*"
}
}

Some files were not shown because too many files have changed in this diff Show More