This commit is contained in:
Ivan Kopeykin 2022-04-05 10:56:31 +03:00
parent d6d6fb584d
commit 0ee3a992e8
7 changed files with 168 additions and 81 deletions

View File

@ -24,8 +24,6 @@ const { parseResource } = require("./util/identifier");
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
const fileURLToPathTag = Symbol("fileURLToPath");
class NodeStuffPlugin {
/**
* @param {NodeOptions} options options
@ -116,33 +114,20 @@ class NodeStuffPlugin {
});
};
let moduleInCache;
let functionNameCached;
const setUrlModuleConstant = (expressionName, fn) => {
parser.hooks.expression
.for(expressionName)
.tap("NodeStuffPlugin", expr => {
if (moduleInCache !== parser.state.current) {
moduleInCache = parser.state.current;
if (parser.isVariableDefined("fileURLToPath")) {
for (let i = 0; i < 30; i++) {
const name = `fileURLToPath${i}`;
if (!parser.isVariableDefined(name)) {
functionNameCached = name;
break;
}
}
} else {
functionNameCached = "fileURLToPath";
}
parser.tagVariable(functionNameCached, fileURLToPathTag, {});
}
const dep = new ExternalModuleDependency(
"url",
[{ name: "fileURLToPath", value: functionNameCached }],
fn(functionNameCached),
[
{
name: "fileURLToPath",
value: "__webpack_fileURLToPath__"
}
],
undefined,
fn("__webpack_fileURLToPath__"),
expr.range,
expressionName
);
@ -205,7 +190,8 @@ class NodeStuffPlugin {
case "node-module":
setUrlModuleConstant(
"__dirname",
functionName => `${functionName}(import.meta.url + "/..")`
functionName =>
`${functionName}(import.meta.url + "/..").slice(0, -1)`
);
break;
case true:
@ -231,15 +217,6 @@ class NodeStuffPlugin {
"require.extensions is not supported by webpack. Use a loader instead."
)
);
if (
localOptions.__dirname === "node-module" ||
localOptions.__filename === "node-module"
)
parser.hooks.finish.tap(
"NodeStuffPlugin",
() => (moduleInCache = undefined)
);
};
normalModuleFactory.hooks.parser

View File

@ -30,6 +30,13 @@ class CachedConstDependency extends NullDependency {
this._hashUpdate = undefined;
}
/**
* @returns {string} hash update
*/
_createHashUpdate() {
return `${this.identifier}${this.range}${this.expression}`;
}
/**
* Update the hash
* @param {Hash} hash hash to be updated
@ -38,7 +45,7 @@ class CachedConstDependency extends NullDependency {
*/
updateHash(hash, context) {
if (this._hashUpdate === undefined)
this._hashUpdate = "" + this.identifier + this.range + this.expression;
this._hashUpdate = this._createHashUpdate();
hash.update(this._hashUpdate);
}

View File

@ -5,9 +5,9 @@
"use strict";
const InitFragment = require("../InitFragment");
const makeSerializable = require("../util/makeSerializable");
const CachedConstDependency = require("./CachedConstDependency");
const ExternalModuleInitFragment = require("./ExternalModuleInitFragment");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
@ -17,40 +17,44 @@ const CachedConstDependency = require("./CachedConstDependency");
/** @typedef {import("../util/Hash")} Hash */
class ExternalModuleDependency extends CachedConstDependency {
constructor(module, imports, expression, range, identifier) {
constructor(
module,
importSpecifiers,
defaultImport,
expression,
range,
identifier
) {
super(expression, range, identifier);
if (imports.length === 0) throw new Error("Imports should be provided");
this.importedModule = module;
this.imports = imports;
this.specifiers = importSpecifiers;
this.default = defaultImport;
}
/**
* Update the hash
* @param {Hash} hash hash to be updated
* @param {UpdateHashContext} context context
* @returns {void}
* @returns {string} hash update
*/
updateHash(hash, context) {
if (!this._hashUpdate)
this._hashUpdate = `${this.importedModule}${JSON.stringify(
this.imports
)}${this.identifier}${this.range}${this.expression}`;
hash.update(this._hashUpdate);
_createHashUpdate() {
return `${this.importedModule}${JSON.stringify(this.specifiers)}${
this.default || "null"
}${super._createHashUpdate()}`;
}
serialize(context) {
super.serialize(context);
const { write } = context;
write(this.importedModule);
write(this.imports);
write(this.specifiers);
write(this.default);
}
deserialize(context) {
super.deserialize(context);
const { read } = context;
this.importedModule = read();
this.imports = read();
this.specifiers = read();
this.default = read();
}
}
@ -73,32 +77,11 @@ ExternalModuleDependency.Template = class ExternalModuleDependencyTemplate exten
const dep = /** @type {ExternalModuleDependency} */ (dependency);
const { chunkInitFragments } = templateContext;
let importsString;
const namedImports = [];
for (const { name, value } of dep.imports) {
if (name === "default") {
importsString = value || dep.importedModule;
} else {
namedImports.push(value !== name ? `${name} as ${value}` : name);
}
}
if (namedImports.length > 0) {
const named = `{${namedImports.join(",")}}`;
importsString = importsString ? `${importsString}, ${named}` : named;
}
importsString = `import ${importsString} from ${JSON.stringify(
dep.importedModule
)};`;
chunkInitFragments.push(
new InitFragment(
importsString,
InitFragment.STAGE_CONSTANTS,
0,
importsString
new ExternalModuleInitFragment(
dep.importedModule,
dep.specifiers,
dep.default
)
);
}

View File

@ -0,0 +1,117 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Ivan Kopeykin @vankop
*/
"use strict";
const InitFragment = require("../InitFragment");
const makeSerializable = require("../util/makeSerializable");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../Generator").GenerateContext} Context */
/** @typedef {Map<string, Set<string>>} ImportSpecifiers */
class ExternalModuleInitFragment extends InitFragment {
/**
* @param {string} importedModule imported module
* @param {Array<{ name: string, value?: string }>|ImportSpecifiers} specifiers import specifiers
* @param {string=} defaultImport default import
*/
constructor(importedModule, specifiers, defaultImport) {
super(
undefined,
InitFragment.STAGE_CONSTANTS,
0,
`external module imports|${importedModule}|${defaultImport || "null"}`
);
this.importedModule = importedModule;
if (Array.isArray(specifiers)) {
/** @type {ImportSpecifiers} */
this.specifiers = new Map();
for (const { name, value } of specifiers) {
let specifiers = this.specifiers.get(name);
if (!specifiers) {
specifiers = new Set();
this.specifiers.set(name, specifiers);
}
specifiers.add(value || name);
}
} else {
this.specifiers = specifiers;
}
this.defaultImport = defaultImport;
}
merge(other) {
const newSpecifiersMap = new Map(this.specifiers);
for (const [name, specifiers] of other.specifiers) {
if (newSpecifiersMap.has(name)) {
const currentSpecifiers = newSpecifiersMap.get(name);
for (const spec of specifiers) currentSpecifiers.add(spec);
} else {
newSpecifiersMap.set(name, specifiers);
}
}
return new ExternalModuleInitFragment(
this.importedModule,
newSpecifiersMap,
this.defaultImport
);
}
/**
* @param {Context} context context
* @returns {string|Source} the source code that will be included as initialization code
*/
getContent({ runtimeRequirements }) {
const namedImports = [];
for (const [name, specifiers] of this.specifiers) {
for (const spec of specifiers) {
if (spec === name) {
namedImports.push(name);
} else {
namedImports.push(`${name} as ${spec}`);
}
}
}
let importsString =
namedImports.length > 0 ? `{${namedImports.join(",")}}` : "";
if (this.defaultImport) {
importsString = `${this.defaultImport}${
importsString ? `, ${importsString}` : ""
}`;
}
return `import ${importsString} from ${JSON.stringify(
this.importedModule
)};`;
}
serialize(context) {
super.serialize(context);
const { write } = context;
write(this.importedModule);
write(this.specifiers);
write(this.defaultImport);
}
deserialize(context) {
super.deserialize(context);
const { read } = context;
this.importedModule = read();
this.specifiers = read();
this.defaultImport = read();
}
}
makeSerializable(
ExternalModuleInitFragment,
"webpack/lib/dependencies/ExternalModuleInitFragment"
);
module.exports = ExternalModuleInitFragment;

View File

@ -47,6 +47,8 @@ module.exports = {
require("../dependencies/CachedConstDependency"),
"dependencies/ExternalModuleDependency": () =>
require("../dependencies/ExternalModuleDependency"),
"dependencies/ExternalModuleInitFragment": () =>
require("../dependencies/ExternalModuleInitFragment"),
"dependencies/CreateScriptUrlDependency": () =>
require("../dependencies/CreateScriptUrlDependency"),
"dependencies/CommonJsRequireContextDependency": () =>

View File

@ -1,4 +1,6 @@
const fileURLToPath = "";
const file = __filename;
const dir = __dirname;
const dir2 = `${__dirname}/`;
module.exports = { file, dir };
module.exports = { file, dir, dir2 };

View File

@ -1,11 +1,10 @@
import { dir, file } from './cjs/file.js'
import { dir, dir2, file } from './cjs/file.js'
it("should generate correct __dirname", () => {
const match = dir.match(/[\\/][^\\/]+[\\/]$/);
expect(match && match[0]).toMatch(/[\\/]node-globals[\\/]/);
expect(dir).toMatch(/[\\/]node-globals$/);
expect(dir2).toMatch(/[\\/]node-globals\/$/);
});
it("should generate correct __filename", () => {
const match = file.match(/[\\/][^\\/]+$/);
expect(match && match[0]).toMatch(/[\\/]main.mjs$/);
expect(file).toMatch(/[\\/]main.mjs$/);
});