From 35807ca01803cc132cae965911f6c7836014abe1 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Thu, 4 Feb 2021 23:17:20 +0100 Subject: [PATCH] add very basic library type "module" --- examples/module/README.md | 81 ++++++++++++--- examples/module/example.js | 2 + examples/module/webpack.config.js | 5 +- lib/ExportsInfo.js | 1 + lib/library/AbstractLibraryPlugin.js | 109 +++++++++++++++------ lib/library/EnableLibraryPlugin.js | 8 +- lib/library/ExportPropertyLibraryPlugin.js | 10 ++ lib/library/ModuleLibraryPlugin.js | 100 +++++++++++++++++++ types.d.ts | 6 ++ 9 files changed, 273 insertions(+), 49 deletions(-) create mode 100644 lib/library/ModuleLibraryPlugin.js diff --git a/examples/module/README.md b/examples/module/README.md index f277a5f4be6..1070ace1342 100644 --- a/examples/module/README.md +++ b/examples/module/README.md @@ -10,6 +10,8 @@ inc(); print(value); resetCounter(); print(value); + +export { inc, print }; ``` # methods.js @@ -39,11 +41,52 @@ export function reset() { ```javascript /******/ "use strict"; +/******/ // The require scope +/******/ var __webpack_require__ = {}; +/******/ +``` + +
/* webpack runtime code */ + +``` js +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/************************************************************************/ +``` + +
+ +``` js +var __webpack_exports__ = {}; /*!********************************!*\ !*** ./example.js + 2 modules ***! \********************************/ /*! namespace exports */ -/*! runtime requirements: */ +/*! export inc [provided] [used in main] [could be renamed] -> ./counter.js .increment */ +/*! export print [provided] [used in main] [could be renamed] -> ./methods.js .print */ +/*! runtime requirements: __webpack_exports__, __webpack_require__.d, __webpack_require__.* */ + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "inc": () => (/* reexport */ increment), + "print": () => (/* reexport */ print) +}); ;// CONCATENATED MODULE: ./counter.js let value = 0; @@ -72,12 +115,18 @@ increment(); print(value); counter_reset(); print(value); + + + +var __webpack_exports__inc = __webpack_exports__.inc; +var __webpack_exports__print = __webpack_exports__.print; +export { __webpack_exports__inc as inc, __webpack_exports__print as print }; ``` # dist/output.js (production) ```javascript -let o=0;function n(){o++}const c=o=>console.log(o);c(o),n(),n(),n(),c(o),o=0,c(o); +var e={d:(o,r)=>{for(var t in r)e.o(r,t)&&!e.o(o,t)&&Object.defineProperty(o,t,{enumerable:!0,get:r[t]})},o:(e,o)=>Object.prototype.hasOwnProperty.call(e,o)},o={};e.d(o,{a:()=>t,S:()=>a});let r=0;function t(){r++}const a=e=>console.log(e);a(r),t(),t(),t(),a(r),r=0,a(r);var n=o.a,c=o.S;export{n as inc,c as print}; ``` # Info @@ -85,25 +134,29 @@ let o=0;function n(){o++}const c=o=>console.log(o);c(o),n(),n(),n(),c(o),o=0,c(o ## Unoptimized ``` -asset output.js 580 bytes [emitted] [javascript module] (name: main) -chunk (runtime: main) output.js (main) 429 bytes [entry] [rendered] +asset output.js 2.05 KiB [emitted] [javascript module] (name: main) +chunk (runtime: main) output.js (main) 453 bytes (javascript) 396 bytes (runtime) [entry] [rendered] > ./example.js main - ./example.js + 2 modules 429 bytes [built] [code generated] - [no exports] - [no exports used] + runtime modules 396 bytes 2 modules + ./example.js + 2 modules 453 bytes [built] [code generated] + [exports: inc, print] + [all exports used] entry ./example.js main -webpack 5.11.1 compiled successfully + used as library export +webpack 5.20.2 compiled successfully ``` ## Production mode ``` -asset output.js 82 bytes [emitted] [javascript module] [minimized] (name: main) -chunk (runtime: main) output.js (main) 429 bytes [entry] [rendered] +asset output.js 314 bytes [emitted] [javascript module] [minimized] (name: main) +chunk (runtime: main) output.js (main) 453 bytes (javascript) 396 bytes (runtime) [entry] [rendered] > ./example.js main - ./example.js + 2 modules 429 bytes [built] [code generated] - [no exports] - [no exports used] + runtime modules 396 bytes 2 modules + ./example.js + 2 modules 453 bytes [built] [code generated] + [exports: inc, print] + [all exports used] entry ./example.js main -webpack 5.11.1 compiled successfully + used as library export +webpack 5.20.2 compiled successfully ``` diff --git a/examples/module/example.js b/examples/module/example.js index ec782d96733..29e215a8009 100644 --- a/examples/module/example.js +++ b/examples/module/example.js @@ -7,3 +7,5 @@ inc(); print(value); resetCounter(); print(value); + +export { inc, print }; diff --git a/examples/module/webpack.config.js b/examples/module/webpack.config.js index f354df368ff..0f10a6d3d39 100644 --- a/examples/module/webpack.config.js +++ b/examples/module/webpack.config.js @@ -1,6 +1,9 @@ module.exports = { output: { - module: true + module: true, + library: { + type: "module" + } }, optimization: { usedExports: true, diff --git a/lib/ExportsInfo.js b/lib/ExportsInfo.js index 7d8b1cdf644..6b85eb5ec4e 100644 --- a/lib/ExportsInfo.js +++ b/lib/ExportsInfo.js @@ -395,6 +395,7 @@ class ExportsInfo { setAllKnownExportsUsed(runtime) { let changed = false; for (const exportInfo of this._exports.values()) { + if (!exportInfo.provided) continue; if (exportInfo.setUsed(UsageState.Used, runtime)) { changed = true; } diff --git a/lib/library/AbstractLibraryPlugin.js b/lib/library/AbstractLibraryPlugin.js index f1a7a694821..c126eb9609b 100644 --- a/lib/library/AbstractLibraryPlugin.js +++ b/lib/library/AbstractLibraryPlugin.js @@ -17,6 +17,7 @@ const JavascriptModulesPlugin = require("../javascript/JavascriptModulesPlugin") /** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ /** @typedef {import("../util/Hash")} Hash */ const COMMON_LIBRARY_NAME_MESSAGE = @@ -52,28 +53,36 @@ class AbstractLibraryPlugin { apply(compiler) { const { _pluginName } = this; compiler.hooks.thisCompilation.tap(_pluginName, compilation => { - compilation.hooks.finishModules.tap(_pluginName, () => { - for (const [ - name, - { - dependencies: deps, - options: { library } - } - ] of compilation.entries) { - const options = this._parseOptionsCached( - library !== undefined ? library : compilation.outputOptions.library - ); - if (options !== false) { - const dep = deps[deps.length - 1]; - if (dep) { - const module = compilation.moduleGraph.getModule(dep); - if (module) { - this.finishEntryModule(module, name, { options, compilation }); + compilation.hooks.finishModules.tap( + { name: _pluginName, stage: 10 }, + () => { + for (const [ + name, + { + dependencies: deps, + options: { library } + } + ] of compilation.entries) { + const options = this._parseOptionsCached( + library !== undefined + ? library + : compilation.outputOptions.library + ); + if (options !== false) { + const dep = deps[deps.length - 1]; + if (dep) { + const module = compilation.moduleGraph.getModule(dep); + if (module) { + this.finishEntryModule(module, name, { + options, + compilation + }); + } } } } } - }); + ); const getOptionsForChunk = chunk => { if (compilation.chunkGraph.getNumberOfEntryModules(chunk) === 0) @@ -85,23 +94,47 @@ class AbstractLibraryPlugin { ); }; - compilation.hooks.additionalChunkRuntimeRequirements.tap( - _pluginName, - (chunk, set) => { - const options = getOptionsForChunk(chunk); - if (options !== false) { - this.runtimeRequirements(chunk, set, { options, compilation }); + if ( + this.render !== AbstractLibraryPlugin.prototype.render || + this.runtimeRequirements !== + AbstractLibraryPlugin.prototype.runtimeRequirements + ) { + compilation.hooks.additionalChunkRuntimeRequirements.tap( + _pluginName, + (chunk, set) => { + const options = getOptionsForChunk(chunk); + if (options !== false) { + this.runtimeRequirements(chunk, set, { options, compilation }); + } } - } - ); + ); + } const hooks = JavascriptModulesPlugin.getCompilationHooks(compilation); - hooks.render.tap(_pluginName, (source, renderContext) => { - const options = getOptionsForChunk(renderContext.chunk); - if (options === false) return source; - return this.render(source, renderContext, { options, compilation }); - }); + if (this.render !== AbstractLibraryPlugin.prototype.render) { + hooks.render.tap(_pluginName, (source, renderContext) => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return source; + return this.render(source, renderContext, { options, compilation }); + }); + } + + if ( + this.renderStartup !== AbstractLibraryPlugin.prototype.renderStartup + ) { + hooks.renderStartup.tap( + _pluginName, + (source, module, renderContext) => { + const options = getOptionsForChunk(renderContext.chunk); + if (options === false) return source; + return this.renderStartup(source, module, renderContext, { + options, + compilation + }); + } + ); + } hooks.chunkHash.tap(_pluginName, (chunk, hash, context) => { const options = getOptionsForChunk(chunk); @@ -151,7 +184,8 @@ class AbstractLibraryPlugin { * @returns {void} */ runtimeRequirements(chunk, set, libraryContext) { - set.add(RuntimeGlobals.returnExportsFromRuntime); + if (this.render !== AbstractLibraryPlugin.prototype.render) + set.add(RuntimeGlobals.returnExportsFromRuntime); } /** @@ -164,6 +198,17 @@ class AbstractLibraryPlugin { return source; } + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup(source, module, renderContext, libraryContext) { + return source; + } + /** * @param {Chunk} chunk the chunk * @param {Hash} hash hash diff --git a/lib/library/EnableLibraryPlugin.js b/lib/library/EnableLibraryPlugin.js index 5fed0806db6..0e00750d09b 100644 --- a/lib/library/EnableLibraryPlugin.js +++ b/lib/library/EnableLibraryPlugin.js @@ -209,9 +209,13 @@ class EnableLibraryPlugin { }).apply(compiler); break; } - case "module": - // TODO implement module library + case "module": { + const ModuleLibraryPlugin = require("./ModuleLibraryPlugin"); + new ModuleLibraryPlugin({ + type + }).apply(compiler); break; + } default: throw new Error(`Unsupported library type ${type}. Plugins which provide custom library types must call EnableLibraryPlugin.setEnabled(compiler, type) to disable this error.`); diff --git a/lib/library/ExportPropertyLibraryPlugin.js b/lib/library/ExportPropertyLibraryPlugin.js index 46112ee7d12..3e7b61cfc63 100644 --- a/lib/library/ExportPropertyLibraryPlugin.js +++ b/lib/library/ExportPropertyLibraryPlugin.js @@ -14,6 +14,7 @@ const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); /** @typedef {import("webpack-sources").Source} Source */ /** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ /** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ /** @typedef {import("../Compiler")} Compiler */ /** @typedef {import("../Module")} Module */ /** @typedef {import("../javascript/JavascriptModulesPlugin").RenderContext} RenderContext */ @@ -84,6 +85,15 @@ class ExportPropertyLibraryPlugin extends AbstractLibraryPlugin { } moduleGraph.addExtraReason(module, "used as library export"); } + + /** + * @param {Chunk} chunk the chunk + * @param {Set} set runtime requirements + * @param {LibraryContext} libraryContext context + * @returns {void} + */ + runtimeRequirements(chunk, set, libraryContext) {} + /** * @param {Source} source source * @param {RenderContext} renderContext render context diff --git a/lib/library/ModuleLibraryPlugin.js b/lib/library/ModuleLibraryPlugin.js new file mode 100644 index 00000000000..2690d9c7aca --- /dev/null +++ b/lib/library/ModuleLibraryPlugin.js @@ -0,0 +1,100 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +const { ConcatSource } = require("webpack-sources"); +const Template = require("../Template"); +const propertyAccess = require("../util/propertyAccess"); +const AbstractLibraryPlugin = require("./AbstractLibraryPlugin"); + +/** @typedef {import("webpack-sources").Source} Source */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryOptions} LibraryOptions */ +/** @typedef {import("../../declarations/WebpackOptions").LibraryType} LibraryType */ +/** @typedef {import("../Chunk")} Chunk */ +/** @typedef {import("../Compilation").ChunkHashContext} ChunkHashContext */ +/** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../Module")} Module */ +/** @typedef {import("../javascript/JavascriptModulesPlugin").StartupRenderContext} StartupRenderContext */ +/** @typedef {import("../util/Hash")} Hash */ +/** @template T @typedef {import("./AbstractLibraryPlugin").LibraryContext} LibraryContext */ + +/** + * @typedef {Object} ModuleLibraryPluginOptions + * @property {LibraryType} type + */ + +/** + * @typedef {Object} ModuleLibraryPluginParsed + * @property {string} name + */ + +/** + * @typedef {ModuleLibraryPluginParsed} T + * @extends {AbstractLibraryPlugin} + */ +class ModuleLibraryPlugin extends AbstractLibraryPlugin { + /** + * @param {ModuleLibraryPluginOptions} options the plugin options + */ + constructor(options) { + super({ + pluginName: "ModuleLibraryPlugin", + type: options.type + }); + } + + /** + * @param {LibraryOptions} library normalized library option + * @returns {T | false} preprocess as needed by overriding + */ + parseOptions(library) { + const { name } = library; + if (name) { + throw new Error( + `Library name must unset. ${AbstractLibraryPlugin.COMMON_LIBRARY_NAME_MESSAGE}` + ); + } + return { + name: /** @type {string} */ (name) + }; + } + + /** + * @param {Source} source source + * @param {Module} module module + * @param {StartupRenderContext} renderContext render context + * @param {LibraryContext} libraryContext context + * @returns {Source} source with library export + */ + renderStartup( + source, + module, + { moduleGraph, chunk }, + { options, compilation } + ) { + const result = new ConcatSource(source); + const exportsInfo = moduleGraph.getExportsInfo(module); + const exports = []; + for (const exportInfo of exportsInfo.orderedExports) { + if (!exportInfo.provided) continue; + const varName = `__webpack_exports__${Template.toIdentifier( + exportInfo.name + )}`; + result.add( + `var ${varName} = __webpack_exports__${propertyAccess([ + exportInfo.getUsedName(exportInfo.name, chunk.runtime) + ])};\n` + ); + exports.push(`${varName} as ${exportInfo.name}`); + } + if (exports.length > 0) { + result.add(`export { ${exports.join(", ")} };\n`); + } + return result; + } +} + +module.exports = ModuleLibraryPlugin; diff --git a/types.d.ts b/types.d.ts index 8eee76af46a..7dae801accd 100644 --- a/types.d.ts +++ b/types.d.ts @@ -128,6 +128,12 @@ declare class AbstractLibraryPlugin { renderContext: RenderContextObject, libraryContext: LibraryContext ): Source; + renderStartup( + source: Source, + module: Module, + renderContext: StartupRenderContext, + libraryContext: LibraryContext + ): Source; chunkHash( chunk: Chunk, hash: Hash,